【Linux 平台总线驱动开发实战】

发布于:2025-06-26 ⋅ 阅读:(21) ⋅ 点赞:(0)


在 Linux 驱动开发领域,平台总线驱动是连接硬件设备与内核的重要桥梁,它通过将设备和驱动分离管理,极大提升了驱动的复用性和系统的可维护性。本文将深入剖析平台总线驱动的工作原理,并结合完整代码示例,帮助开发者掌握其核心开发流程。

一、平台总线驱动基础概念

Linux 平台总线驱动基于设备、驱动和总线三者的协同工作,形成了一套高效的设备管理机制:

设备(Device): 代表具体的硬件实体,描述设备名称、资源占用(如内存地址、中断号)等信息。
驱动(Driver): 包含操作硬件的核心代码,通过probe、remove等回调函数完成设备初始化与资源释放。
总线(Bus): 负责两者的注册、匹配与通信,确保驱动能正确识别并控制设备。

平台总线的核心优势在于分离设备与驱动,同一驱动可适配多种同类设备,设备升级时仅需修改设备描述,无需改动驱动代码,显著提升开发效率。

二、核心数据结构解析

2.1 设备结构体 struct platform_device

struct platform_device {
    const char  *name;      // 设备名称,用于驱动匹配
    int         id;         // 设备ID(-1表示自动分配)
    struct device dev;      // 通用设备结构
    u32         num_resources;  // 资源数量
    struct resource *resource;  // 硬件资源数组
    // 其他字段
};

通过填充resource数组,可指定设备的内存、中断等资源。例如:

static struct resource my_gpio_resources[] = {
    [0] = {
       .start = 0xFDD60000,       // GPIO控制器基地址
       .end   = 0xFDD60004,
       .flags = IORESOURCE_MEM,
	},
    [1] = {
       .start = 15,       
       .end   = 15,
       .flags = IORESOURCE_IRQ,
	}

};

2.2 驱动结构体 struct platform_driver

struct platform_driver {
    int (*probe)(struct platform_device *);  // 设备匹配成功时调用
    int (*remove)(struct platform_device *); // 设备移除时调用
    struct device_driver driver;             // 通用驱动结构
    const struct platform_device_id *id_table; // 支持的设备ID表
    // 其他字段
};

probe函数是驱动核心,用于初始化设备;id_table定义驱动支持的设备列表,辅助总线完成匹配。

2.3 资源结构体 struct resource

struct resource {
    resource_size_t start;  // 资源起始地址
    resource_size_t end;    // 资源结束地址
    unsigned long flags;    // 资源类型(如`IORESOURCE_MEM`、`IORESOURCE_IRQ`)
    // 其他字段
};

三、驱动开发完整流程

3.1 设备注册

定义设备资源: 通过struct resource数组描述硬件资源。
初始化设备结构体: 填充struct platform_device,指定名称、ID 和资源。
注册设备到总线: 调用platform_device_register函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>

// 设备名称(用于匹配驱动)
#define DEVICE_NAME "my_gpio_controller"

// 定义设备资源(内存区域和中断)
static struct resource my_gpio_resources[] = {
    [0] = {
       .start = 0xFDD60000,       // GPIO控制器基地址
       .end   = 0xFDD60004,
       .flags = IORESOURCE_MEM,
	},
    [1] = {
       .start = 15,       
       .end   = 15,
       .flags = IORESOURCE_IRQ,
	}

};

static void my_gpio_release(struct device *dev){
    
    printk(KERN_ERR "my_gpio_release\n");
}

// 平台设备结构体
static struct platform_device my_gpio_device = {
    .name           = DEVICE_NAME,
    .id             = -1,          // 自动分配ID
    .num_resources  = ARRAY_SIZE(my_gpio_resources),
    .resource       = my_gpio_resources,
    .dev            = {
        .release    = my_gpio_release
    },
};

// 模块初始化函数
static int __init platform_device_init(void) {
    int ret;
    
    // 注册平台设备
    ret = platform_device_register(&my_gpio_device);
    if (ret) {
        printk(KERN_ERR "Failed to register platform device: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "Platform device registered: %s\n", DEVICE_NAME);
    return 0;
}

// 模块退出函数
static void __exit platform_device_exit(void) {
    // 注销平台设备
    platform_device_unregister(&my_gpio_device);
    printk(KERN_INFO "Platform device unregistered\n");
}

module_init(platform_device_init);
module_exit(platform_device_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Platform Device");
MODULE_AUTHOR("cmy");

3.2 驱动注册

实现驱动回调函数: 编写probe、remove等核心函数。
初始化驱动结构体: 指定驱动名称、支持的设备列表等。
注册驱动到总线: 调用platform_driver_register函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/device.h>

// 驱动支持的设备名称
#define DRIVER_NAME "my_gpio_controller"

// 寄存器偏移量
#define GPIO_SWPORT_DDR 0x0008 //输入输出偏移量
#define GPIO_SWPORT_DR 0x0000 //高低电平偏移量

// 设备私有数据结构
struct gpio_dev {
    struct device *dev;
    dev_t dev_num;
    void __iomem *regs;         // 映射后的寄存器基址
    int irq;                    // 中断号
    struct class *class;        // 设备类
	struct cdev cdev;			// 字符设备结构
};

// 文件操作函数
static ssize_t gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
    struct gpio_dev *gdev = filp->private_data;
    int value = 0;
    
    if (!gdev->regs) 
    {
        printk(KERN_ERR "gpio_read gdev->regs can't be null\n");
        return -EFAULT;
    }
    
        
    u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DR);
    value = (gp_cfg >> 15) & 1;//获取第15位

    printk(KERN_INFO "gpio_read value = %d\n", value);
    if (copy_to_user(buf, &value, sizeof(int))) {
        return -EFAULT;
    }
    
    return 0;
}

static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
    struct gpio_dev *gdev = filp->private_data;
    
	int value = 0;
    if (copy_from_user(&value, buf, sizeof(int))) {
        return -EFAULT;
    }
    if (!gdev->regs) 
    {
        printk(KERN_ERR "gpio_write gdev->regs can't be null\n");
        return -EFAULT;
    }
    
    u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DR);
    printk(KERN_INFO "gpio_write GPIO_SWPORT_DR gp_cfg = %llx\n", gp_cfg);
    if(value)
    {
        gp_cfg |= 1 << 31;//Write access enable
        gp_cfg |= 1 << 15;//high

    }
    else
    {
        gp_cfg |= 1 << 31;//Write access enable
        printk(KERN_INFO " gpio_write Write access gp_cfg = %llx\n", gp_cfg);
        gp_cfg &= ~(1 << 15);//low
        printk(KERN_INFO " gpio_write low gp_cfg = %llx\n", gp_cfg);
    }

    
    iowrite64(gp_cfg, gdev->regs + GPIO_SWPORT_DR);
    
    printk(KERN_INFO " gpio_write gp_cfg = %llx\n", gp_cfg);

    return 0;
}

static int gpio_open(struct inode *inode, struct file *filp) {
    struct gpio_dev *gdev = container_of(inode->i_cdev, struct gpio_dev, cdev);
    filp->private_data = gdev;
    return 0;
}

static int gpio_release(struct inode *inode, struct file *filp) {
    return 0;
}

// 文件操作表
static const struct file_operations gpio_fops = {
    .owner = THIS_MODULE,
    .open = gpio_open,
    .read = gpio_read,
    .write = gpio_write,
    .release = gpio_release,
};



// 驱动probe函数(设备匹配成功时调用)
static int gpio_probe(struct platform_device *pdev) {
    struct gpio_dev *gdev;
    struct resource *res;
    int ret;
    
    // 分配并初始化设备结构体
    gdev = devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL);
    if (!gdev) {
        dev_err(&pdev->dev, "Failed to allocate memory\n");
        return -ENOMEM;
    }
    
    gdev->dev = &pdev->dev;
    platform_set_drvdata(pdev, gdev);
    
    // 获取内存资源并映射
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    printk(KERN_INFO "gpio_probe res->start = %llx, res->end = %llx\n", res->start, res->end);
    /*if (!res) {
        dev_err(&pdev->dev, "Missing memory resource\n");
        return -ENODEV;
    }
    
    gdev->regs = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(gdev->regs)) {
        dev_err(&pdev->dev, "Failed to map memory resource\n");
        return PTR_ERR(gdev->regs);
    }*/

    
    // 注册字符设备
    ret = alloc_chrdev_region(&gdev->dev_num, 0, 1, pdev->name);
    if (ret) {
        dev_err(&pdev->dev, "Failed to allocate char device region\n");
        return ret;
    }
    
    cdev_init(&gdev->cdev, &gpio_fops);
    gdev->cdev.owner = THIS_MODULE;

    ret = cdev_add(&gdev->cdev, gdev->dev_num, 1);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add char device\n");
        unregister_chrdev_region(gdev->dev_num, 1);
        return ret;
    }
    
    // 创建设备类
    gdev->class = class_create(THIS_MODULE, pdev->name);
    if (IS_ERR(gdev->class)) {
        dev_err(&pdev->dev, "Failed to create class\n");
        cdev_del(&gdev->cdev);
        unregister_chrdev_region(gdev->dev_num, 1);
        return PTR_ERR(gdev->class);
    }
    
    // 创建设备节点
    gdev->dev = device_create(gdev->class, NULL, gdev->dev_num, NULL, pdev->name);
    if (IS_ERR(gdev->dev)) {
        dev_err(&pdev->dev, "Failed to create device\n");
        class_destroy(gdev->class);
        cdev_del(&gdev->cdev);
        unregister_chrdev_region(gdev->dev_num, 1);
        return PTR_ERR(gdev->dev);
    }

    // 映射GPIO寄存器
    gdev->regs = ioremap(res->start, res->end - res->start);
    if (!gdev->regs) {
        printk(KERN_ERR "gpio_probe Failed to ioremap\n");
        cdev_del(&gdev->cdev);
        unregister_chrdev_region(gdev->dev_num, 1);
        return -ENOMEM;
    }
    printk(KERN_INFO "gpio_probe gdev->regs = %llx\n", gdev->regs);


    u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DDR);
    printk(KERN_INFO "gpio_probe GPIO_SWPORT_DDR gp_cfg = %llx\n", gp_cfg);
    gp_cfg |= 1 << 31;// Write access enable
    gp_cfg |= 1 << 15;//Output

    //设置输出模式
    iowrite64(gp_cfg, gdev->regs + GPIO_SWPORT_DDR);
    gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DDR);


    printk(KERN_INFO "gpio_probe initialized successfully gp_cfg = %llx\n", gp_cfg);


    
    dev_info(&pdev->dev, "GPIO driver initialized\n");
    return 0;
}

// 驱动remove函数(设备移除时调用)
static int gpio_remove(struct platform_device *pdev) {
    struct gpio_dev *gdev = platform_get_drvdata(pdev);
    if (gdev->regs) {
        iounmap(gdev->regs);  // 解除映射
        gdev->regs = NULL;
    }
    
    // 清理资源
    device_destroy(gdev->class, gdev->dev_num);
    class_destroy(gdev->class);
    cdev_del(&gdev->cdev);
    unregister_chrdev_region(gdev->dev_num, 1);
    
    dev_info(&pdev->dev, "GPIO driver removed\n");
    return 0;
}

// 驱动支持的设备ID表
 const struct platform_device_id gpio_device_id = {
    .name = DRIVER_NAME,
};


// 平台驱动结构体
static struct platform_driver gpio_driver = {
    .probe      = gpio_probe,
    .remove     = gpio_remove,
    .driver     = {
        .name   = DRIVER_NAME,
        .owner  = THIS_MODULE,
    },
    .id_table   = &gpio_device_id,
};

// 模块初始化
static int __init gpio_driver_init(void) {
    return platform_driver_register(&gpio_driver);
}

// 模块退出
static void __exit gpio_driver_exit(void) {
    platform_driver_unregister(&gpio_driver);
}

module_init(gpio_driver_init);
module_exit(gpio_driver_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Platform Driver");
MODULE_AUTHOR("cmy");

3.3 设备与驱动匹配

总线通过设备和驱动的name字段进行匹配,匹配成功后自动调用驱动的probe函数初始化设备。若id_table存在,总线会优先检查设备是否在支持列表中。

四、编译与测试

4.1 编译模块

创建Makefile:

export ARCH=arm64

export CROSS_COMPILE=/home/chenmy/rk356x/RK356X_Android11.0/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-

obj-m += platform_device_example.o

obj-m += platform_driver_example.o

KERNEL_DIR:=/home/chenmy/rk356x/RK356X_Android11.0/kernel

all:
	make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
	make -C $(KERNEL_DIR) M=$(PWD) clean

执行make生成.ko模块文件。

4.2 加载与验证

 # 加载设备模块
insmod platform_device_example.ko
# 加载驱动模块
insmod platform_driver_example.ko

# 查看设备与驱动状态
ls /sys/bus/platform/devices | grep "my"
ls /sys/bus/platform/drivers | grep "my"

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、总结

Linux 平台总线驱动通过标准化的设备与驱动分离模型,显著提升了驱动开发的效率与代码复用性。本文通过原理解析与完整代码示例,展示了从设备注册、驱动实现到匹配测试的全流程。在实际开发中,开发者可根据硬件需求灵活调整资源配置与回调函数逻辑,构建稳定高效的驱动程序。


网站公告

今日签到

点亮在社区的每一天
去签到