Linux 驅動程式是連線硬體和軟體的橋樑,本文旨在引導讀者由淺入深地學習 Linux 驅動程式的開發。從最基礎的字元裝置驅動開始,逐步深入 I2C、SPI、USB 等核心子系統,搭配 Raspberry Pi 等嵌入式平台的實作,讓讀者能實際操作並理解驅動程式運作的細節。本文也涵蓋了中斷處理、DMA 等進階議題,提供讀者更全面的驅動程式開發知識。

工業I/O子系統與輸入子系統的深入解析

在嵌入式系統與Linux驅動程式開發中,工業I/O(IIO)子系統與輸入子系統扮演著至關重要的角色。本章將探討這兩個子系統的核心概念、實作方法及其在實際應用中的價值。

工業I/O子系統的核心架構

工業I/O子系統是Linux核心中用於處理類別比數位轉換器(ADC)、數位類別比轉換器(DAC)及其他相關裝置的重要元件。其設計目標是為工業控制和測量應用提供統一的介面。

IIO裝置的sysfs介面

IIO子系統透過sysfs提供使用者空間的介面,使應用程式能夠存取和控制IIO裝置。這些介面包括:

  1. 裝置通道屬性:提供每個通道的詳細資訊,如資料型別、縮放因子等。
  2. 緩衝區管理:支援資料的批次讀取和寫入,提高系統效率。

IIO緩衝區與觸發器

緩衝區機制允許高效地傳輸大量資料,而觸發器則用於控制資料擷取的時序和條件。透過合理組態觸發器,可以實作精確的資料採集。

輸入子系統的原理與應用

輸入子系統是Linux處理輸入裝置(如鍵盤、滑鼠、觸控式螢幕等)的核心框架。它提供了一套統一的介面,使得驅動程式開發者能夠方便地實作各種輸入裝置的驅動。

輸入裝置的註冊與事件處理

  1. 裝置註冊:輸入裝置透過input_register_device函式註冊到輸入子系統。
  2. 事件上報:當輸入裝置產生事件時,驅動程式將事件上報給輸入子系統,再由子系統將事件分發給使用者空間的應用程式。

實驗室練習:實作Nunchuk輸入裝置

本實驗旨在實作一個根據Nunchuk的遊戲控制器輸入裝置驅動,並透過輸入子系統將其整合到Linux系統中。

硬體描述

Nunchuk是一種流行的遊戲控制器,具有加速度計和按鍵等輸入功能。透過I2C介面與主機通訊。

裝置樹組態

在裝置樹中,需要正確組態I2C匯流排和Nunchuk裝置的相關引數,如匯流排地址、中斷腳位等。

驅動實作

驅動程式需要完成以下任務:

  1. 初始化I2C通訊。
  2. 設定Nunchuk的工作模式。
  3. 讀取Nunchuk的狀態,並將輸入事件上報給輸入子系統。

實驗室練習:根據SPI的加速度計輸入裝置

另一個實驗是實作一個根據SPI介面的加速度計輸入裝置驅動。

SPI子系統簡介

SPI(Serial Peripheral Interface)是一種常見的同步串列通訊協定,用於主機與外設之間的高速資料交換。Linux的SPI子系統提供了對SPI裝置的統一管理。

驅動實作關鍵點

  1. SPI裝置註冊:在裝置樹中宣告SPI裝置,並在驅動程式中註冊。
  2. 資料傳輸:使用SPI API進行資料的讀寫操作。
  3. 輸入事件上報:將加速度計的資料轉換為輸入事件,並上報給輸入子系統。

使用Regmap API於裝置驅動程式的深度解析

在Linux核心中,Regmap API提供了一種通用的介面,用於簡化對各種硬體暫存器的存取操作。無論是SPI、I2C還是其他匯流排,Regmap都能有效地管理和最佳化暫存器的讀寫操作。本文將探討Regmap API在裝置驅動程式中的應用,並透過實際範例進行詳細說明。

Regmap API的核心優勢

Regmap API的主要優勢在於其能夠:

  1. 簡化暫存器存取:提供統一的介面來讀寫不同匯流排上的裝置暫存器。
  2. 最佳化效能:透過快取和批次操作減少匯流排交易的次數,提高系統效率。
  3. 提升可維護性:將暫存器存取邏輯與驅動程式的其他部分分離,使程式碼更易於理解和維護。

在裝置驅動程式中使用Regmap API

要在裝置驅動程式中使用Regmap API,首先需要初始化一個struct regmap結構體。這個結構體包含了裝置的暫存器對映資訊以及對應的存取函式。

struct regmap *regmap_init_spi(struct spi_device *spi, const struct regmap_config *config);

內容解密:

  • regmap_init_spi函式用於初始化SPI裝置的Regmap。
  • struct spi_device *spi是指向SPI裝置結構的指標。
  • const struct regmap_config *config包含了Regmap的組態資訊,如暫存器的數量、大小等。

實務範例:ADXL345加速計驅動程式

以下是一個使用Regmap API的ADXL345加速計驅動程式範例:

#include <linux/regmap.h>
#include <linux/spi/spi.h>

static const struct regmap_config adxl345_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = ADXL345_REG_DEVID,
};

static int adxl345_probe(struct spi_device *spi)
{
    struct regmap *regmap;

    regmap = regmap_init_spi(spi, &adxl345_regmap_config);
    if (IS_ERR(regmap)) {
        dev_err(&spi->dev, "Failed to initialize regmap\n");
        return PTR_ERR(regmap);
    }

    // 使用regmap進行暫存器操作
    regmap_write(regmap, ADXL345_REG_POWER_CTL, 0x08); // 開啟測量模式

    return 0;
}

內容解密:

  1. 定義了adxl345_regmap_config結構體,用於組態Regmap的引數。
    • .reg_bits = 8表示暫存器位址是8位元。
    • .val_bits = 8表示暫存器值是8位元。
    • .max_register = ADXL345_REG_DEVID指定了最大暫存器位址。
  2. adxl345_probe函式中,呼叫regmap_init_spi初始化SPI裝置的Regmap。
  3. 使用regmap_write函式將值寫入ADXL345的POWER_CTL暫存器,以開啟測量模式。

USB裝置驅動程式開發

USB(通用序列匯流排)是一種廣泛使用的外設介面標準。Linux核心提供了豐富的USB子系統,用於支援各種USB裝置。本文將介紹如何開發USB裝置驅動程式。

USB裝置描述符

USB裝置透過一系列描述符來向主機宣告其功能和特性。主要的描述符包括:

  • 裝置描述符(Device Descriptor)
  • 組態描述符(Configuration Descriptor)
  • 介面描述符(Interface Descriptor)
  • 端點描述符(Endpoint Descriptor)

編寫Linux USB裝置驅動程式

開發USB裝置驅動程式的第一步是註冊一個USB驅動程式結構體:

static struct usb_driver usb_led_driver = {
    .name = "usb_led",
    .probe = usb_led_probe,
    .disconnect = usb_led_disconnect,
    .id_table = usb_led_id_table,
};

內容解密:

  • .name指定了驅動程式的名稱。
  • .probe函式在裝置被檢測到時呼叫,用於初始化裝置。
  • .disconnect函式在裝置斷開連線時呼叫,用於清理資源。
  • .id_table列出了該驅動程式支援的USB裝置。

資料傳輸:USB請求區塊(URB)

USB資料傳輸透過USB請求區塊(URB)進行。URB代表了一次USB傳輸請求,可以用於控制傳輸、中斷傳輸、批次傳輸等。

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

內容解密:

  • usb_alloc_urb函式用於分配一個URB。
  • int iso_packets指定了等時傳輸的封包數量。
  • gfp_t mem_flags指定了記憶體分配的旗標。

實務範例:USB LED控制驅動程式

以下是一個簡單的USB LED控制驅動程式範例:

static int usb_led_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(interface);
    struct usb_endpoint_descriptor *endpoint;

    // 找到合適的端點
    endpoint = &interface->cur_altsetting->endpoint[0].desc;

    // 分配並初始化URB
    struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
    // ... 設定URB ...

    usb_submit_urb(urb, GFP_KERNEL);

    return 0;
}

內容解密:

  1. usb_led_probe函式中,取得USB裝置和端點描述符。
  2. 分配並初始化一個URB,用於後續的資料傳輸。
  3. 使用usb_submit_urb提交URB,開始資料傳輸。

前言

嵌入式系統已成為我們日常生活不可或缺的一部分。它們被廣泛應用於行動裝置、網路基礎設施、家居和消費性電子產品、數位看板、醫療影像、車載資訊娛樂系統等眾多工業領域。嵌入式系統的使用正在呈指數級增長。許多嵌入式系統都由價格低廉但功能強大的片上系統(SoC)驅動,並執行Linux作業系統。來自Broadcom的BCM2837就是其中一款SoC,它搭載了四核ARM Cortex A53處理器,主頻達1.2GHz。這正是流行的Raspberry Pi 3開發板所採用的SoC。

本文遵循「邊做邊學」的教學方法,因此您將從第一章開始就與Raspberry Pi互動。除了Raspberry Pi開發板外,您還將使用幾款低成本的開發板來開發實作範例。在實驗室中,每一步驟都會被詳細描述,以便您能夠根據自己的硬體元件調整書中的內容。

您將學習如何為Raspberry Pi開發板開發Linux驅動程式。首先,您將從最簡單的不與外部硬體互動的驅動程式開始,接著開發能夠管理不同型別裝置的Linux驅動程式,例如:加速度計、數位類別比轉換器(DAC)、類別比數位轉換器(ADC)、RGB LED、按鈕、搖桿控制器、多顯示LED控制器,以及透過I2C和SPI匯流排控制的I/O擴充套件器。您還將開發DMA驅動程式、USB裝置驅動程式、處理中斷的驅動程式,以及寫入和讀取SoC內部暫存器以控制其GPIO的驅動程式。為了簡化部分驅動程式的開發,您將使用不同型別的Linux核心子系統:雜項(Miscellaneous)、LED、UIO、USB、輸入(Input)和工業I/O(Industrial I/O)。本文總共編寫了超過30個核心模組(以及多個使用者應用程式),這些內容都可以從本文的GitHub儲存函式庫下載。

本文使用長期支援(LTS)的Linux核心5.4,該版本於2019年11月發布,並將持續維護至2025年12月。

本文是一本入門書,旨在幫助您在沒有任何先前經驗的情況下開始開發驅動程式。因此,在撰寫過程中,我們盡量開發出複雜度不高的驅動程式,既能強化主要的驅動程式開發概念,又能作為您開發自有驅動程式的起點。並且,請記住,開發驅動程式的最佳方法不是從零開始。您可以重用來自相似的Linux核心主線驅動程式的免費程式碼。本文中編寫的所有驅動程式都採用GPL授權,因此您可以在相同的授權下修改和重新發布它們。

本文結構

第一章「建構系統」首先描述了嵌入式Linux系統的主要組成部分。接著,詳細介紹如何為Raspberry Pi建構嵌入式Linux系統。您將在Micro SD卡上安裝根據核心5.4.y的Raspberry Pi OS映像,然後從rpi-5.4.y分支下載Raspberry Pi核心原始碼,組態核心以滿足開發驅動程式所需設定,建構核心,最後將新核心安裝在Raspberry Pi Linux映像上。所產生的Linux映像將用於本文中驅動程式和應用程式的測試。

第二章「Linux裝置和驅動程式模型」闡述了「匯流排」驅動程式、「匯流排控制器」驅動程式和「裝置」驅動程式之間的關係。同時,也對裝置樹(Device Tree)和sysfs檔案系統進行了介紹。

第三章「最簡單的驅動程式」涵蓋了幾個尚未透過「系統呼叫」與使用者應用程式互動的簡單驅動程式。本章將幫助您驗證您的驅動程式開發環境是否正常運作。

第四章「字元驅動程式」描述了字元驅動程式的架構。它解釋瞭如何透過系統呼叫從使用者空間呼叫驅動程式的操作,以及如何在核心和使用者空間之間交換資料。同時,也說明瞭如何識別和建立每個Linux裝置。本章將編寫多個使用不同方法與使用者空間交換資訊的驅動程式,以建立裝置節點。第一個驅動程式將使用傳統的靜態裝置建立方法,即使用「mknod」命令;第二個驅動程式將展示如何使用「devtmpfs」建立裝置檔案;最後一個驅動程式則將使用「雜項框架」建立裝置檔案。本章還解釋瞭如何在sysfs下建立裝置類別和裝置驅動程式專案。

詳細內容解說:

第四章涵蓋的主題包括:

  • 字元驅動程式的基本架構
  • 如何透過系統呼叫與使用者空間互動
  • 使用不同方法建立裝置節點
  • 在sysfs下建立裝置類別和裝置驅動程式專案

程式碼範例:

#include <linux/module.h>
#include <linux/init.h>

static int __init my_init(void) {
    printk(KERN_INFO "Hello, world!\n");
    return 0;
}

static void __exit my_exit(void) {
    printk(KERN_INFO "Goodbye, world!\n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux kernel module");

內容解密:

此範例展示了一個簡單的Linux核心模組。它包含了初始化函式my_init和離開函式my_exit,分別在模組載入和解除安裝時被呼叫。使用printk函式輸出資訊到核心日誌。module_initmodule_exit巨集用於註冊這些函式。最後,巨集MODULE_LICENSEMODULE_AUTHORMODULE_DESCRIPTION提供了關於模組的中繼資料。

此圖示顯示了Linux核心模組的基本結構:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Linux驅動程式開發

package "物聯網架構" {
    package "感知層" {
        component [感測器] as sensor
        component [執行器] as actuator
        component [嵌入式裝置] as device
    }

    package "網路層" {
        component [閘道器] as gateway
        component [MQTT Broker] as mqtt
        component [邊緣運算] as edge
    }

    package "平台層" {
        cloud "IoT Platform" as platform
        database [時序資料庫] as tsdb
        component [規則引擎] as rules
    }

    package "應用層" {
        component [監控儀表板] as dashboard
        component [告警系統] as alert
        component [數據分析] as analytics
    }
}

sensor --> device : 資料採集
device --> gateway : 資料傳輸
gateway --> mqtt : MQTT 協議
mqtt --> edge : 邊緣處理
edge --> platform : 雲端上傳
platform --> tsdb : 資料儲存
platform --> rules : 規則處理
rules --> alert : 觸發告警
tsdb --> analytics : 資料分析
analytics --> dashboard : 視覺化

@enduml

此圖示說明瞭核心模組的生命週期,從初始化到離開。詳細解說如下:

  • 初始化階段:註冊初始化函式,該函式在模組載入時執行。
  • 離開階段:註冊離開函式,該函式在模組解除安裝時執行。
  • 中繼資料定義:提供模組的相關資訊,如授權、作者和描述等。

本文內容簡介與開發環境設定

本文主要介紹如何在Linux環境下開發裝置驅動程式,涵蓋了從基礎的字元裝置驅動到複雜的USB、I2C、SPI等子系統的驅動開發。書中透過多個實際的實驗室範例,指導讀者如何為Raspberry Pi等嵌入式裝置撰寫驅動程式。

本文章節概述

  1. 字元裝置驅動基礎:介紹Linux下字元裝置驅動的基本概念和開發方法。
  2. I2C子系統:深入講解I2C協定及其在Linux下的實作,包含如何撰寫I2C客戶端驅動以及如何透過sysfs控制硬體。
  3. 中斷處理:探討嵌入式處理器上的中斷硬體和軟體操作,包含中斷控制器的驅動開發和GPIO中斷處理。
  4. 記憶體管理:解釋Linux下的不同型別的地址和記憶體分配器。
  5. DMA在裝置驅動中的應用:介紹Linux DMA引擎子系統,並開發一個用於Raspberry Pi的DMA驅動。
  6. 輸入子系統:介紹如何使用框架為不同型別的裝置提供一致的使用者空間介面,重點講解輸入子系統和SPI子系統。
  7. 工業I/O子系統:描述IIO子系統對ADC、DAC等裝置的支援,包含觸發緩衝區和工業I/O事件的設定。
  8. Regmap API的使用:概述regmap API及其在簡化SPI和I2C子系統呼叫中的作用。
  9. USB裝置驅動:介紹Linux USB子系統,並指導如何開發自定義USB HID裝置驅動。

開發所需的硬體裝置

為了充分利用本文的內容,讀者需要準備以下硬體:

  1. Raspberry Pi板(支援Model B、Model B+和Model 4)
  2. MikroElektronika Color click配件板
  3. PCF8574 IO擴充套件板
  4. Analog Devices LTC3206 I2C多顯示板DC749A
  5. MikroElektronika Button R click板
  6. MikroElektronika EXPAND 6 click板
  7. MikroElektronika Accel click板
  8. Olimex MOD-Wii-UEXT-NUNCHUCK
  9. MikroElektronika PIXI click板
  10. Microchip Curiosity PIC32MX470開發板

下載資源

讀者可以從GitHub倉函式庫下載本文相關的Linux核心模組、驅動程式、應用程式和裝置樹檔案。

聯絡方式

若讀者在學習過程中遇到任何問題,可以透過電子郵件aliberal@arroweurope.com聯絡作者團隊。