linux驅動框架與驅動開發實戰
- Linux驅動框架與驅動開發實戰
- 一、Linux驅動框架概述
- 1.1 Linux驅動的分類
- 1.2 Linux驅動的基本框架
- 二、Linux驅動關鍵API詳解
- 2.1 模塊相關API
- 2.2 字符設備驅動API
- 2.3 內存管理API
- 2.4 中斷處理API
- 2.5 PCI設備驅動API
- 三、Xilinx XDMA驅動開發詳解
- 3.1 XDMA概述
- 3.2 XDMA驅動開發步驟
- 步驟1:定義PCI設備ID
- 步驟2:定義驅動主結構體
- 步驟3:實現PCI probe函數
- 步驟4:實現文件操作接口
- 步驟5:實現中斷處理
- 步驟6:實現DMA傳輸
- 步驟7:實現remove函數
- 步驟8:定義PCI驅動結構體并注冊
- 3.3 步驟總結
- 四、XDMA驅動測試與調試
- 4.1 加載驅動模塊
- 4.2 測試DMA傳輸
- 4.3 常見問題調試
- 五、性能優化技巧
- 5.1 使用分散/聚集DMA
- 5.2 實現零拷貝
- 5.3 使用DMA池
- 六、總結
Linux驅動框架與驅動開發實戰
一、Linux驅動框架概述
Linux驅動是操作系統內核與硬件設備之間的橋梁,它使得硬件設備能夠被操作系統識別和管理。Linux內核提供了一套完善的驅動框架,開發者可以基于這些框架開發各種硬件設備的驅動程序。
1.1 Linux驅動的分類
Linux驅動主要分為以下幾類:
- 字符設備驅動:以字節流形式進行數據讀寫,如鍵盤、鼠標等
- 塊設備驅動:以數據塊為單位進行讀寫,如硬盤、SSD等
- 網絡設備驅動:用于網絡通信的設備,如網卡
- 其他特殊類型:如USB驅動、PCI驅動等框架驅動
Linux驅動模型分層:
1.2 Linux驅動的基本框架
無論哪種類型的驅動,Linux都提供了相應的框架和接口。一個典型的Linux驅動包含以下組成部分:
- 模塊加載和卸載函數:
module_init()
和module_exit()
- 文件操作接口:
file_operations
結構體 - 設備注冊與注銷:
register_chrdev()
等函數 - 中斷處理:
request_irq()
和中斷處理函數 - 內存管理:
kmalloc()
,ioremap()
等函數 - 同步機制:自旋鎖、信號量、互斥鎖等
二、Linux驅動關鍵API詳解
2.1 模塊相關API
module_init(init_function); // 指定模塊加載時執行的函數
module_exit(exit_function); // 指定模塊卸載時執行的函數
MODULE_LICENSE("GPL"); // 聲明模塊許可證
MODULE_AUTHOR("Author"); // 聲明模塊作者
MODULE_DESCRIPTION("Desc"); // 聲明模塊描述
2.2 字符設備驅動API
// 注冊字符設備
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);// 注銷字符設備
void unregister_chrdev(unsigned int major, const char *name);// 文件操作結構體
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);// 其他操作...
};
2.3 內存管理API
// 內核內存分配
void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *objp);// 物理地址映射
void *ioremap(phys_addr_t offset, unsigned long size);
void iounmap(void *addr);// 用戶空間與內核空間數據拷貝
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
2.4 中斷處理API
// 申請中斷
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);// 釋放中斷
void free_irq(unsigned int irq, void *dev_id);// 中斷處理函數原型
irqreturn_t irq_handler(int irq, void *dev_id);
2.5 PCI設備驅動API
// PCI設備ID表
static const struct pci_device_id ids[] = {{ PCI_DEVICE(VENDOR_ID, DEVICE_ID) },{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);// PCI驅動結構體
static struct pci_driver pci_driver = {.name = "xdma_driver",.id_table = ids,.probe = xdma_probe,.remove = xdma_remove,// 其他回調...
};// 注冊PCI驅動
pci_register_driver(&pci_driver);// 注銷PCI驅動
pci_unregister_driver(&pci_driver);
三、Xilinx XDMA驅動開發詳解
3.1 XDMA概述
Xilinx DMA (XDMA) 是一種高性能的DMA控制器,用于在FPGA和主機內存之間傳輸數據。XDMA驅動通常作為PCIe設備驅動實現,支持DMA傳輸、中斷處理等功能。
其實現DMA傳輸流程如下:
3.2 XDMA驅動開發步驟
步驟1:定義PCI設備ID
#define PCI_VENDOR_ID_XILINX 0x10ee
#define PCI_DEVICE_ID_XDMA 0x7028static const struct pci_device_id xdma_pci_ids[] = {{ PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XDMA) },{ 0, }
};
MODULE_DEVICE_TABLE(pci, xdma_pci_ids);
步驟2:定義驅動主結構體
struct xdma_dev {struct pci_dev *pdev;void __iomem *bar[MAX_BARS]; // PCI BAR空間映射int irq; // 中斷號struct cdev cdev; // 字符設備dev_t devno; // 設備號struct dma_chan *dma_chan; // DMA通道// 其他設備特定數據...
};
步驟3:實現PCI probe函數
PCI設備探測流程:
具體探測函數(probe)實現:
static int xdma_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{struct xdma_dev *xdev;int err, i;// 1. 分配設備結構體xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL);if (!xdev)return -ENOMEM;xdev->pdev = pdev;pci_set_drvdata(pdev, xdev);// 2. 使能PCI設備err = pci_enable_device(pdev);if (err) {dev_err(&pdev->dev, "Failed to enable PCI device\n");goto fail;}// 3. 請求PCI資源err = pci_request_regions(pdev, "xdma");if (err) {dev_err(&pdev->dev, "Failed to request PCI regions\n");goto disable_device;}// 4. 映射BAR空間for (i = 0; i < MAX_BARS; i++) {if (!pci_resource_len(pdev, i))continue;xdev->bar[i] = pci_iomap(pdev, i, pci_resource_len(pdev, i));if (!xdev->bar[i]) {dev_err(&pdev->dev, "Failed to map BAR%d\n", i);err = -ENOMEM;goto release_regions;}}// 5. 設置DMA掩碼err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));if (err) {err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));if (err) {dev_err(&pdev->dev, "No suitable DMA available\n");goto unmap_bars;}}// 6. 申請中斷xdev->irq = pdev->irq;err = request_irq(xdev->irq, xdma_irq_handler, IRQF_SHARED, "xdma", xdev);if (err) {dev_err(&pdev->dev, "Failed to request IRQ\n");goto unmap_bars;}// 7. 初始化DMA引擎err = xdma_init_dma(xdev);if (err)goto free_irq;// 8. 注冊字符設備err = xdma_setup_cdev(xdev);if (err)goto deinit_dma;dev_info(&pdev->dev, "XDMA driver loaded successfully\n");return 0;// 錯誤處理...
}// 初始化DMA引擎
static int xdma_init_dma(struct xdma_dev *xdev)
{dma_cap_mask_t mask;dma_cap_zero(mask);dma_cap_set(DMA_MEMCPY, mask);xdev->dma_chan = dma_request_channel(mask, NULL, NULL);if (!xdev->dma_chan) {dev_err(&xdev->pdev->dev, "Failed to get DMA channel\n");return -ENODEV;}return 0;
}// 設置字符設備
static int xdma_setup_cdev(struct xdma_dev *xdev)
{int err;dev_t devno;err = alloc_chrdev_region(&devno, 0, 1, "xdma");if (err < 0) {dev_err(&xdev->pdev->dev, "Failed to allocate device number\n");return err;}xdev->devno = devno;cdev_init(&xdev->cdev, &xdma_fops);xdev->cdev.owner = THIS_MODULE;err = cdev_add(&xdev->cdev, devno, 1);if (err) {dev_err(&xdev->pdev->dev, "Failed to add cdev\n");unregister_chrdev_region(devno, 1);return err;}return 0;
}
步驟4:實現文件操作接口
static const struct file_operations xdma_fops = {.owner = THIS_MODULE,.open = xdma_open,.release = xdma_release,.read = xdma_read,.write = xdma_write,.unlocked_ioctl = xdma_ioctl,.llseek = no_llseek,
};static int xdma_open(struct inode *inode, struct file *filp)
{struct xdma_dev *xdev = container_of(inode->i_cdev, struct xdma_dev, cdev);filp->private_data = xdev;return 0;
}static int xdma_release(struct inode *inode, struct file *filp)
{filp->private_data = NULL;return 0;
}static ssize_t xdma_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{struct xdma_dev *xdev = filp->private_data;// 實現DMA讀取操作...return count;
}static ssize_t xdma_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{struct xdma_dev *xdev = filp->private_data;// 實現DMA寫入操作...return count;
}static long xdma_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct xdma_dev *xdev = filp->private_data;switch (cmd) {case XDMA_IOCTL_START_DMA:// 啟動DMA傳輸break;case XDMA_IOCTL_STOP_DMA:// 停止DMA傳輸break;case XDMA_IOCTL_GET_STATUS:// 獲取DMA狀態break;default:return -ENOTTY;}return 0;
}
步驟5:實現中斷處理
static irqreturn_t xdma_irq_handler(int irq, void *dev_id)
{struct xdma_dev *xdev = dev_id;u32 status;// 讀取中斷狀態寄存器status = ioread32(xdev->bar[0] + XDMA_IRQ_STATUS_REG);if (status & XDMA_IRQ_DONE) {// DMA傳輸完成中斷complete(&xdev->dma_complete);}if (status & XDMA_IRQ_ERROR) {// DMA錯誤中斷dev_err(&xdev->pdev->dev, "DMA error occurred\n");}// 清除中斷狀態iowrite32(status, xdev->bar[0] + XDMA_IRQ_STATUS_REG);return IRQ_HANDLED;
}
步驟6:實現DMA傳輸
static int xdma_do_transfer(struct xdma_dev *xdev, dma_addr_t src, dma_addr_t dst, size_t len)
{struct dma_async_tx_descriptor *tx;struct dma_device *dma_dev = xdev->dma_chan->device;enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;dma_cookie_t cookie;int err;// 準備DMA描述符tx = dma_dev->device_prep_dma_memcpy(xdev->dma_chan, dst, src, len, flags);if (!tx) {dev_err(&xdev->pdev->dev, "Failed to prepare DMA descriptor\n");return -EIO;}tx->callback = xdma_dma_callback;tx->callback_param = xdev;// 提交DMA傳輸cookie = dmaengine_submit(tx);err = dma_submit_error(cookie);if (err) {dev_err(&xdev->pdev->dev, "Failed to submit DMA transfer\n");return err;}// 觸發DMA傳輸dma_async_issue_pending(xdev->dma_chan);// 等待傳輸完成if (!wait_for_completion_timeout(&xdev->dma_complete, msecs_to_jiffies(1000))) {dev_err(&xdev->pdev->dev, "DMA transfer timeout\n");dmaengine_terminate_all(xdev->dma_chan);return -ETIMEDOUT;}return 0;
}static void xdma_dma_callback(void *data)
{struct xdma_dev *xdev = data;complete(&xdev->dma_complete);
}
步驟7:實現remove函數
static void xdma_remove(struct pci_dev *pdev)
{struct xdma_dev *xdev = pci_get_drvdata(pdev);int i;// 1. 移除字符設備cdev_del(&xdev->cdev);unregister_chrdev_region(xdev->devno, 1);// 2. 釋放DMA資源if (xdev->dma_chan)dma_release_channel(xdev->dma_chan);// 3. 釋放中斷free_irq(xdev->irq, xdev);// 4. 取消BAR空間映射for (i = 0; i < MAX_BARS; i++) {if (xdev->bar[i])pci_iounmap(pdev, xdev->bar[i]);}// 5. 釋放PCI資源pci_release_regions(pdev);// 6. 禁用PCI設備pci_disable_device(pdev);// 7. 釋放設備結構體devm_kfree(&pdev->dev, xdev);dev_info(&pdev->dev, "XDMA driver unloaded\n");
}
步驟8:定義PCI驅動結構體并注冊
static struct pci_driver xdma_driver = {.name = "xdma",.id_table = xdma_pci_ids,.probe = xdma_probe,.remove = xdma_remove,
};static int __init xdma_init(void)
{return pci_register_driver(&xdma_driver);
}static void __exit xdma_exit(void)
{pci_unregister_driver(&xdma_driver);
}module_init(xdma_init);
module_exit(xdma_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Xilinx XDMA Driver");
3.3 步驟總結
上文以xilinx XDMA 為例介紹了Linux PCI設備驅動開發步驟,總結成流程圖如下:
四、XDMA驅動測試與調試
4.1 加載驅動模塊
# 加載驅動
sudo insmod xdma.ko# 查看加載的模塊
lsmod | grep xdma# 查看內核日志
dmesg | tail
4.2 測試DMA傳輸
可以使用簡單的用戶空間程序測試DMA功能:
// test_xdma.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>#define XDMA_DEV "/dev/xdma"
#define BUF_SIZE (1024 * 1024) // 1MBint main()
{int fd = open(XDMA_DEV, O_RDWR);if (fd < 0) {perror("Failed to open device");return -1;}// 分配測試緩沖區char *src = malloc(BUF_SIZE);char *dst = malloc(BUF_SIZE);if (!src || !dst) {perror("Failed to allocate buffers");close(fd);return -1;}// 填充源緩沖區memset(src, 0xAA, BUF_SIZE);memset(dst, 0, BUF_SIZE);// 寫入數據到設備ssize_t written = write(fd, src, BUF_SIZE);printf("Written %zd bytes to device\n", written);// 從設備讀取數據ssize_t readed = read(fd, dst, BUF_SIZE);printf("Read %zd bytes from device\n", readed);// 驗證數據if (memcmp(src, dst, BUF_SIZE) {printf("Data verification failed!\n");} else {printf("Data verification passed!\n");}free(src);free(dst);close(fd);return 0;
}
4.3 常見問題調試
-
PCI設備未識別:
- 檢查
lspci -nn
確認設備ID是否正確 - 確認內核配置中啟用了PCI支持
- 檢查
-
DMA傳輸失敗:
- 檢查DMA掩碼設置
- 確認物理地址是否正確
- 檢查DMA引擎是否支持所需操作
-
中斷不觸發:
- 確認中斷號是否正確
- 檢查中斷狀態寄存器
- 確認中斷處理函數已正確注冊
五、性能優化技巧
5.1 使用分散/聚集DMA
static int xdma_sg_transfer(struct xdma_dev *xdev, struct scatterlist *sg_src,struct scatterlist *sg_dst,int sg_count)
{struct dma_async_tx_descriptor *tx;struct dma_device *dma_dev = xdev->dma_chan->device;enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;dma_cookie_t cookie;int err;tx = dma_dev->device_prep_dma_sg(xdev->dma_chan, sg_dst, sg_count,sg_src, sg_count,flags);if (!tx) {dev_err(&xdev->pdev->dev, "Failed to prepare SG DMA descriptor\n");return -EIO;}tx->callback = xdma_dma_callback;tx->callback_param = xdev;cookie = dmaengine_submit(tx);err = dma_submit_error(cookie);if (err) {dev_err(&xdev->pdev->dev, "Failed to submit SG DMA transfer\n");return err;}dma_async_issue_pending(xdev->dma_chan);if (!wait_for_completion_timeout(&xdev->dma_complete, msecs_to_jiffies(1000))) {dev_err(&xdev->pdev->dev, "SG DMA transfer timeout\n");dmaengine_terminate_all(xdev->dma_chan);return -ETIMEDOUT;}return 0;
}
5.2 實現零拷貝
static int xdma_mmap(struct file *filp, struct vm_area_struct *vma)
{struct xdma_dev *xdev = filp->private_data;unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;unsigned long size = vma->vm_end - vma->vm_start;int ret;// 將BAR空間映射到用戶空間if (offset >= pci_resource_len(xdev->pdev, 0) || size > pci_resource_len(xdev->pdev, 0) - offset) {return -EINVAL;}ret = remap_pfn_range(vma, vma->vm_start,(pci_resource_start(xdev->pdev, 0) + offset) >> PAGE_SHIFT,size, vma->vm_page_prot);if (ret)return -EAGAIN;return 0;
}
5.3 使用DMA池
// 初始化DMA池
xdev->dma_pool = dma_pool_create("xdma_pool", &xdev->pdev->dev,POOL_SIZE, POOL_ALIGN, 0);
if (!xdev->dma_pool) {dev_err(&xdev->pdev->dev, "Failed to create DMA pool\n");return -ENOMEM;
}// 從DMA池分配內存
void *buf = dma_pool_alloc(xdev->dma_pool, GFP_KERNEL, &dma_handle);
if (!buf) {dev_err(&xdev->pdev->dev, "Failed to allocate from DMA pool\n");return -ENOMEM;
}// 釋放DMA池內存
dma_pool_free(xdev->dma_pool, buf, dma_handle);// 銷毀DMA池
dma_pool_destroy(xdev->dma_pool);
六、總結
本文詳細介紹了Linux驅動框架和關鍵API,并以Xilinx XDMA驅動為例,展示了Linux驅動開發的完整流程。關鍵點包括:
- 理解Linux驅動框架:掌握字符設備、塊設備和網絡設備驅動的基本結構
- 熟悉關鍵API:模塊加載、文件操作、內存管理、中斷處理等核心API
- PCI驅動開發:從設備發現到資源管理的完整流程
- DMA傳輸實現:包括標準DMA和分散/聚集DMA
- 驅動調試技巧:日志分析、用戶空間測試程序等
通過XDMA驅動的實例,我們可以看到Linux驅動開發需要綜合考慮硬件特性、內核API和性能優化等多個方面。希望本文能為Linux驅動開發者提供有價值的參考。