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驱动开发者提供有价值的参考。