[实战] linux驱动框架与驱动开发实战

Linux驱动框架与驱动开发实战

一、Linux驱动框架概述

Linux驱动是操作系统内核与硬件设备之间的桥梁,它使得硬件设备能够被操作系统识别和管理。Linux内核提供了一套完善的驱动框架,开发者可以基于这些框架开发各种硬件设备的驱动程序。

1.1 Linux驱动的分类

Linux驱动主要分为以下几类:

  1. 字符设备驱动:以字节流形式进行数据读写,如键盘、鼠标等
  2. 块设备驱动:以数据块为单位进行读写,如硬盘、SSD等
  3. 网络设备驱动:用于网络通信的设备,如网卡
  4. 其他特殊类型:如USB驱动、PCI驱动等框架驱动

Linux驱动模型分层:

用户空间
系统调用接口
VFS虚拟文件系统
字符设备驱动
块设备驱动
硬件设备

1.2 Linux驱动的基本框架

无论哪种类型的驱动,Linux都提供了相应的框架和接口。一个典型的Linux驱动包含以下组成部分:

  1. 模块加载和卸载函数module_init()module_exit()
  2. 文件操作接口file_operations结构体
  3. 设备注册与注销register_chrdev()等函数
  4. 中断处理request_irq()和中断处理函数
  5. 内存管理kmalloc(), ioremap()等函数
  6. 同步机制:自旋锁、信号量、互斥锁等

二、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传输、中断处理等功能。

PCIe
AXI总线
Host CPU
XDMA引擎
FPGA逻辑

其实现DMA传输流程如下:

User Kernel DMA引擎 write()系统调用 配置源地址/目标地址 传输完成中断 唤醒等待进程 User Kernel 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设备探测流程:

Kernel PCIe设备 驱动 扫描PCI总线 返回Vendor/Device ID 调用probe()函数 Kernel PCIe设备 驱动

具体探测函数(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设备驱动开发步骤,总结成流程图如下:

Yes
No
驱动模块加载
module_init调用
PCI设备探测 probe
探测成功?
资源分配
映射BAR空间
申请中断
初始化DMA引擎
注册字符设备
错误处理
模块退出
用户空间操作
open/read/write
ioctl控制
mmap内存映射
DMA传输处理
中断处理
数据传输完成
module_exit调用
释放资源
注销字符设备
释放DMA资源
解除BAR映射
释放中断
禁用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 常见问题调试

  1. PCI设备未识别

    • 检查lspci -nn确认设备ID是否正确
    • 确认内核配置中启用了PCI支持
  2. DMA传输失败

    • 检查DMA掩码设置
    • 确认物理地址是否正确
    • 检查DMA引擎是否支持所需操作
  3. 中断不触发

    • 确认中断号是否正确
    • 检查中断状态寄存器
    • 确认中断处理函数已正确注册

五、性能优化技巧

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驱动开发的完整流程。关键点包括:

  1. 理解Linux驱动框架:掌握字符设备、块设备和网络设备驱动的基本结构
  2. 熟悉关键API:模块加载、文件操作、内存管理、中断处理等核心API
  3. PCI驱动开发:从设备发现到资源管理的完整流程
  4. DMA传输实现:包括标准DMA和分散/聚集DMA
  5. 驱动调试技巧:日志分析、用户空间测试程序等

通过XDMA驱动的实例,我们可以看到Linux驱动开发需要综合考虑硬件特性、内核API和性能优化等多个方面。希望本文能为Linux驱动开发者提供有价值的参考。

猜你喜欢

转载自blog.csdn.net/jz_ddk/article/details/147015183
今日推荐