linux驱动框架与驱动开发实战
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 0x7028
static 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) // 1MB
int 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驱动开发者提供有价值的参考。