在 Linux 核心開發 USB 驅動程式,需要對 USB 核心子系統和驅動程式模型有一定理解。本文以中斷傳輸為例,說明如何撰寫一個能與 USB 裝置進行雙向通訊的驅動程式。首先,驅動程式需定義裝置的 Vendor ID 和 Product ID,並建立 ID 表格以便系統識別。接著,定義私有資料結構儲存裝置資訊和 URB,並在 probe 函式中初始化這些結構。設定端點、分配 URB,並使用 usb_fill_int_urb 函式填入 URB 的相關引數,例如傳輸緩衝區、長度、回呼函式等。最後,建立 sysfs 檔案讓使用者空間可以讀寫控制裝置。
USB裝置驅動程式開發:以中斷傳輸為例
本章節將介紹如何在Linux系統下開發USB裝置驅動程式,特別是以中斷傳輸(Interrupt Transfer)為例,展示如何與USB裝置進行資料交換。
USB裝置驅動程式基礎
在開始之前,我們需要了解USB裝置驅動程式的基本架構。USB裝置驅動程式主要負責與USB裝置進行通訊,包括資料的傳送和接收。在Linux系統中,USB裝置驅動程式是以模組的形式存在的,可以動態地載入和移除。
中斷傳輸簡介
中斷傳輸是一種用於傳輸小量資料的USB傳輸型別,它保證了資料傳輸的及時性和可靠性。在本例中,我們將使用中斷傳輸來與USB裝置進行通訊。
驅動程式開發步驟
1. 定義USB裝置資訊
首先,我們需要定義USB裝置的廠商ID(Vendor ID)和產品ID(Product ID),這些資訊將用於識別USB裝置。
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
2. 建立USB裝置ID表
接下來,我們需要建立一個USB裝置ID表,用於匹配支援的USB裝置。
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, id_table);
3. 定義私有資料結構
我們需要定義一個私有資料結構,用於儲存與USB裝置相關的資訊。
struct usb_led {
struct usb_device *udev;
struct usb_interface *intf;
struct urb *interrupt_out_urb;
struct urb *interrupt_in_urb;
u8 irq_data;
u8 ibuffer;
int ep_in;
int ep_out;
};
4. 初始化和設定USB裝置
在led_probe函式中,我們將初始化和設定USB裝置,包括分配私有資料結構、設定端點(Endpoint)資訊、以及初始化中斷傳輸URB(USB Request Block)。
static int led_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
// ...
dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
// ...
usb_fill_int_urb(dev->interrupt_out_urb, dev->udev, usb_sndintpipe(dev->udev, ep_out),
(void *)&dev->irq_data, 1, led_urb_out_callback, dev, 1);
usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, usb_rcvintpipe(dev->udev, ep_in),
(void *)&dev->ibuffer, 1, led_urb_in_callback, dev, 1);
// ...
}
5. 處理使用者空間的寫入請求
當使用者空間應用程式寫入到/sys/bus/usb/devices/1-1.3:1.0/led檔案時,led_store函式將被呼叫。該函式將讀取使用者空間傳遞的資料,並透過中斷傳輸URB將資料傳送到USB裝置。
static ssize_t led_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
// ...
kstrtou8(buf, 10, &val);
led->irq_data = val;
retval = usb_submit_urb(led->interrupt_out_urb, GFP_KERNEL);
// ...
}
6. 中斷傳輸URB完成回呼函式
當中斷傳輸URB完成時,led_urb_out_callback和led_urb_in_callback函式將被呼叫。這些函式將檢查URB的狀態,並根據需要重新提交URB。
static void led_urb_out_callback(struct urb *urb)
{
// ...
if (urb->status) {
// 處理錯誤
}
}
static void led_urb_in_callback(struct urb *urb)
{
// ...
if (urb->status) {
// 處理錯誤
} else {
// 處理接收到的資料
if (dev->ibuffer == 0x00)
pr_info("switch is ON.\n");
else if (dev->ibuffer == 0x01)
pr_info("switch is OFF.\n");
else
pr_info("bad value received\n");
usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
}
}
USB 裝置驅動程式開發:以 LED 控制為例
在 Linux 核心中開發 USB 裝置驅動程式是一項複雜但有趣的任務。本文將探討如何開發一個控制 LED 的 USB 裝置驅動程式,涵蓋從初始化到資料傳輸的完整流程。
驅動程式初始化與設定
驅動程式的初始化主要在 led_probe 函式中完成。當 USB 裝置被插入時,核心會呼叫此函式進行初始化。
static int led_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
// 取得 USB 裝置指標
struct usb_device *udev = interface_to_usbdev(intf);
// 尋找最後一個中斷輸出端點
res = usb_find_last_int_out_endpoint(altsetting, &endpoint);
if (res) {
dev_info(&intf->dev, "no endpoint found");
return res;
}
// 組態端點資訊
ep_out = altsetting->endpoint[1].desc.bEndpointAddress;
dev_info(&intf->dev, "endpoint out address is (%d)", ep_out);
// 組態 USB LED 結構
dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->udev = usb_get_dev(udev);
dev->intf = intf;
// 初始化中斷輸出 URB
usb_fill_int_urb(dev->interrupt_out_urb,
dev->udev,
usb_sndintpipe(dev->udev, ep_out),
(void *)&dev->irq_data,
1,
led_urb_out_callback, dev, 1);
// 設定介面資料
usb_set_intfdata(intf, dev);
// 建立裝置檔案
retval = device_create_file(&intf->dev, &dev_attr_led);
if (retval)
goto error_create_file;
// 提交中斷輸入 URB
retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
if (retval) {
dev_err(&dev->udev->dev, "Couldn’t submit interrupt_in_urb %d\n", retval);
device_remove_file(&intf->dev, &dev_attr_led);
goto error_create_file;
}
return 0;
}
內容解密:
led_probe函式的作用:當 USB 裝置插入時,核心會呼叫led_probe進行初始化,包括組態端點、分配記憶體和初始化 URB(USB Request Block)。- 端點組態:透過
usb_find_last_int_out_endpoint尋找中斷輸出端點,並取得其位址。 - URB 初始化:使用
usb_fill_int_urb初始化中斷輸出和輸入 URB,以進行非同步資料傳輸。 - 裝置檔案建立:呼叫
device_create_file建立 sysfs 介面,使用者可透過該檔案控制 LED。 - 錯誤處理:在初始化過程中,若發生錯誤,將釋放已分配的資源並傳回錯誤碼。
資料傳輸實作
資料傳輸是透過 URB 完成的。驅動程式實作了兩個回呼函式:led_urb_out_callback 和 led_urb_in_callback,分別處理輸出和輸入資料傳輸的完成事件。
static void led_urb_out_callback(struct urb *urb)
{
struct usb_led *dev;
dev = urb->context;
dev_info(&dev->udev->dev, "led_urb_out_callback() function is called.\n");
// 檢查 URB 狀態
if (urb->status) {
if (!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(&dev->udev->dev,
"%s - nonzero write status received: %d\n",
__func__, urb->status);
}
}
內容解密:
led_urb_out_callback的作用:當輸出 URB 完成時被呼叫,主要檢查傳輸狀態。- 狀態檢查:若傳輸狀態非零,則記錄錯誤資訊,但忽略特定錯誤碼(如
-ENOENT、-ECONNRESET和-ESHUTDOWN)。
使用者空間控制介面
驅動程式透過 sysfs 提供使用者空間控制介面。使用者可透過寫入特定值到 led 檔案來控制 LED 狀態。
static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
// 將字串轉換為 u8 值
error = kstrtou8(buf, 10, &val);
if (error)
return error;
// 設定 LED 狀態
led->led_number = val;
led->irq_data = val;
// 提交中斷輸出 URB
retval = usb_submit_urb(led->interrupt_out_urb, GFP_KERNEL);
if (retval) {
dev_err(&led->udev->dev, "Couldn’t submit interrupt_out_urb %d\n", retval);
return retval;
}
return count;
}
內容解密:
led_store的作用:當使用者寫入資料到led檔案時被呼叫,用於控制 LED 狀態。- 資料轉換:將使用者寫入的字串轉換為
u8值,並設定 LED 狀態。 - 提交輸出 URB:將資料透過中斷輸出 URB 傳送到 USB 裝置。
USB 裝置驅動程式開發
本章節將探討 USB 裝置驅動程式的開發,特別是在 Linux 環境下的實作與應用。我們將以兩個實驗室(LAB 13.4 和相關的 USB 驅動程式開發)為例,詳細介紹如何撰寫 Linux USB 驅動程式以控制特定的硬體裝置。
USB 驅動程式基礎
在開始之前,我們需要了解 USB 驅動程式的基本架構和工作原理。USB 驅動程式是作業系統用來與 USB 裝置溝通的橋樑,它負責處理 USB 裝置的初始化、資料傳輸等工作。
USB 驅動程式的核心元件
- USB 裝置結構: 描述 USB 裝置的屬性,如廠商 ID、產品 ID 等。
- URB(USB Request Block): 用於描述一次 USB 資料傳輸請求的資料結構。
- 端點(Endpoint): USB 裝置中的資料傳輸端點,可以是輸入或輸出。
實驗室:USB_URB_INT_LED 驅動程式
本實驗室展示瞭如何開發一個簡單的 USB 驅動程式 usb_urb_int_led.ko,用於控制 PIC32MX470 Curiosity 開發板上的 LED。
步驟一:載入驅動程式
首先,將 PIC32MX470 Curiosity 開發板的 USB Micro-B 連線埠連線到 Raspberry Pi 的 USB Host Type-A 連線埠。然後,在 Raspberry Pi 上載入 usb_urb_int_led.ko 驅動程式模組。
root@raspberrypi:/home# insmod usb_urb_int_led.ko
步驟二:控制 LED
載入驅動程式後,可以透過寫入 /sys/bus/usb/devices/1-1.3:1.0/led 檔案來控制 PIC32MX470 Curiosity 開發板上的 LED。
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 1 > led
程式碼解析
usb_urb_int_led.ko 驅動程式
// 當裝置被插入時呼叫
static int led_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
// 初始化 URB
urb = usb_alloc_urb(0, GFP_KERNEL);
// 設定端點資訊
endpoint = usb_find_endpoint(interface, &endpoint_addr);
// 提交 URB 請求
usb_submit_urb(urb, GFP_KERNEL);
return 0;
}
// 當寫入 led 檔案時呼叫
static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
// 解析輸入值
int value = simple_strtol(buf, NULL, 10);
// 設定 LED 狀態
set_led_state(value);
return count;
}
// URB 回撥函式
static void led_urb_out_callback(struct urb *urb)
{
// 處理 URB 結束事件
if (urb->status != 0) {
printk(KERN_ERR "URB error: %d\n", urb->status);
}
}
內容解密:
led_probe函式:當 USB 裝置被插入時,核心會呼叫此函式進行初始化。它負責分配 URB、尋找正確的端點並提交 URB 請求以開始資料傳輸。led_store函式:此函式在使用者寫入/sys/bus/usb/devices/1-1.3:1.0/led檔案時被呼叫。它解析輸入值並設定 LED 狀態。led_urb_out_callback函式:作為 URB 的回撥函式,當 URB 資料傳輸完成或發生錯誤時被呼叫。在此例中,它檢查 URB 的狀態並在發生錯誤時輸出錯誤訊息。
I2C to USB 多顯示 LED 模組實驗室
本實驗室將展示如何開發一個 Linux USB 驅動程式,以使用 I2C Tools for Linux 從使用者空間控制 LTC3206 I2C 多顯示 LED 控制器的裝置。
遞迴驅動模型
此實驗室採用遞迴驅動模型,其中 USB 裝置驅動程式會建立一個 I2C 配接器驅動程式,而 I2C 裝置驅動程式則透過 I2C 核心與 I2C 配接器驅動程式進行通訊。
實作步驟
組態 MPLAB Harmony: 在 MPLAB Harmony 組態工具中啟用 I2C 驅動程式並組態相關的 Pin。
修改產生的程式碼: 在
app.c中修改USB_Task函式,以處理來自主機的 I2C 資料並將其轉發到 LTC3206 裝置。
static void USB_Task(void)
{
if (appData.usbDeviceIsConfigured) {
switch (appData.stateUSB) {
case USB_STATE_WAITING_FOR_DATA:
if (appData.hidDataReceived) {
DRV_I2C_Transmit(appData.drvI2CHandle_Master, 0x36, &appData.receiveDataBuffer[0], 3, NULL);
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
break;
// 其他狀態處理...
}
}
}
內容解密:
USB_Task函式:負責處理 USB 裝置的狀態機。在USB_STATE_WAITING_FOR_DATA狀態下,一旦接收到來自主機的資料,便會透過 I2C 將資料傳輸到 LTC3206 裝置。