Linux内核与驱动:10.平台总线platform

在 Linux 驱动开发中,platform 是最常见、最基础的一类驱动模型。

尤其是在 ARM、嵌入式 Linux、设备树开发里,很多 GPIO、LED、按键、UART、I2C 控制器、SPI 控制器等驱动,最终都会和 platform 打交道。

1.什么是platform?

platform 是 Linux 内核中一种虚拟总线机制,主要用来管理那些片上外设或不依赖真实可枚举总线的设备。

它和 I2C、SPI、USB 不一样。

I2C / SPI / USB,这些总线是真实存在的物理总线:

  • 有总线协议
  • 有设备挂接关系
  • 有设备发现或通信机制

platform 没有一条真实"线"在那里,它更像是一种软件层面的组织方式:

  • 把"设备信息"描述出来
  • 把"驱动"注册进去
  • 然后由内核完成匹配和绑定

所以可以把它理解成:

platform 是 Linux 为"板级设备、SoC 内部设备、片上资源型设备"设计的一种驱动管理框架。

为什么需要platform?

在嵌入式开发里,很多设备其实都集成在 SoC 内部,比如:

  • GPIO 控制器
  • UART 控制器
  • PWM 控制器
  • RTC
  • Watchdog
  • LCD 控制器
  • 音频控制器
  • 中断控制器

这些设备:

  • 不在 PCI 总线上
  • 不是 USB 设备
  • 也不是自动枚举出来的

那内核怎么知道"板子上有这个设备"?

这时就需要一种机制来告诉内核:

  • 这个设备叫什么
  • 它的寄存器地址是多少
  • 它的中断号是多少
  • 它的 GPIO、时钟、复位资源是什么

而 platform 就是为这件事服务的。

2.platform的核心组成

platform 机制里最核心的两个对象是:

platform_device
platform_driver

可以简单对应理解成:

**platform_device :**描述"设备是什么,有哪些资源"。

platform_driver : 描述"驱动怎么初始化这个设备"。

当两者匹配成功后,内核就会调用驱动里的 probe

使用platform也是"设备"与"实现"的分离,是资源解耦的体现,低耦合,高内聚的体现。

3.platform的工作流程

使用platform总线与不使用的区别在于,使用platform总线就是把之前一个程序写完的驱动写成两个程序,device.c和driver.c,device.c中描述设备的信息,driver.c中进行驱动的编写。

platform 的基本流程可以概括成:

设备(platform_device) 注册->驱动(platform_driver) 注册->设备和驱动匹配->匹配成功后调用 probe()

注意:只有device和driver中定义的name一样才能被匹配

3.1platform_device

platform_device定义在device.c中,用来描述设备本身。

常见内容包括:

  • 设备名字
  • 资源信息
  • 内存寄存器地址
  • 中断号
  • DMA 资源
  • 设备树节点信息

早期不使用设备树时,很多板级代码会手动注册 platform_device,例如:

cpp 复制代码
struct platform_device xxx_device = {
    .name = "mydev",
    .id = -1,
    ...
};

但在现在的 ARM/设备树开发里,更常见的情况是:

platform_device 不是你手动写代码注册,而是由设备树自动生成

下面给出一个device.c的最小实现:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>

struct resource my_led_resource[] = {
    [0] = {
        .start = 0xFDD6000,
        .end = 0xFDD6004,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 13,
        .end = 13,
        .flags = IORESOURCE_IRQ,
    }
};

void	my_dev_release(struct device *dev)
{
    printk("this is dev_release\n");
}

struct platform_device platform_led = {
    .name = "my_led",
    .id = -1,
    .resource = my_led_resource,  //resource中存的是device的信息(寄存器地址/中断号等内容)
    .num_resources = ARRAY_SIZE(my_led_resource),
    .dev = {
        .release = my_dev_release,  //dev中必须包含release函数
    },
};
static int platform_device_init(void)
{
    platform_device_register(&platform_led);
}
void platform_device_exit(void)
{
    platform_device_unregister(&platform_led);
}
module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("GPL");

3.2 platform_driver

platform_driver 顾名思义,写在driver.c中,它通常写成这样:

cpp 复制代码
static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "mydev",
    },
};

最重要的是:

其中:

probe:设备和驱动匹配成功时调用,这是驱动初始化的核心入口。

remove:驱动卸载或设备解绑时调用,用于释放资源。

一个driver.c的最小实现:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>

int my_probe(struct platform_device *dev)
{

}
int my_remove(struct platform_device *dev)
{

}
//使用struct platform_device_id要包含头文件mod_devicetable.h
struct platform_device_id my_id_table = {
    .name = "my_led"
};
struct platform_driver my_platform_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_led",
        .owner = THIS_MODULE,
    },
    .id_table = &my_id_table,   //driver和id_table都是声明与device匹配的name的,id_table的优先级更高,相当于大名
};

static int platform_driver_init(void)
{
    platform_driver_register(&my_platform_driver);
}
void platform_driver_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}
module_init(platform_driver_init);
modele_exit(platform_driver_exit);

MODULE_LICENSE("GPL");

Platform 匹配是怎么发生的?

方式一:按名字匹配

传统方式下:

  • platform_device.name
  • platform_driver.driver.name/id_table.name

如果这两个名字相同,就匹配成功。

方式二:按设备树 compatible 匹配

现在最常见的是设备树方式。

4.实验

我们分别给出使用platform与不使用platform的点亮led灯的实验,驱动代码:

不使用platform驱动层代码:

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define  GPIO_DR 0xFDD60000

struct device_test{
   
    dev_t dev_num;  //设备号
     int major ;  //主设备号
    int minor ;  //次设备号
    struct cdev cdev_test; // cdev
    struct class *class;   //类
    struct device *device; //设备
    char kbuf[32];
    unsigned int *vir_gpio_dr;
};

struct  device_test dev1;  


/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data=&dev1;//设置私有数据
    printk("This is cdev_test_open\r\n");

    return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
     struct device_test *test_dev=(struct device_test *)file->private_data;

    if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
    {
        printk("copy_from_user error\r\n");
        return -1;
    }
    if(test_dev->kbuf[0]==1){   //如果应用层传入的数据是1,则打开灯
            *(test_dev->vir_gpio_dr) = 0x8000c040;   //设置数据寄存器的地址
              printk("test_dev->kbuf [0]  is %d\n",test_dev->kbuf[0]);  //打印传入的数据
    }
    else if(test_dev->kbuf[0]==0)  //如果应用层传入的数据是0,则关闭灯
    {
            *(test_dev->vir_gpio_dr) = 0x80004040; //设置数据寄存器的地址
            printk("test_dev->kbuf [0]  is %d\n",test_dev->kbuf[0]); //打印传入的数据
    }
    return 0;
}

/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    struct device_test *test_dev=(struct device_test *)file->private_data;
    
    if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
    {
        printk("copy_to_user error\r\n");
        return -1;
    }

    printk("This is cdev_test_read\r\n");
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release\r\n");
    return 0;
}

/*设备操作函数*/
struct file_operations cdev_test_fops = {
    .owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = cdev_test_open, //将open字段指向chrdev_open(...)函数
    .read = cdev_test_read, //将open字段指向chrdev_read(...)函数
    .write = cdev_test_write, //将open字段指向chrdev_write(...)函数
    .release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};

static int __init chr_fops_init(void) //驱动入口函数
{
    /*注册字符设备驱动*/
    int ret;
    /*1 创建设备号*/
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
    if (ret < 0)
    {
       goto err_chrdev;
    }
    printk("alloc_chrdev_region is ok\n");

    dev1.major = MAJOR(dev1.dev_num); //获取主设备号
   dev1.minor = MINOR(dev1.dev_num); //获取次设备号

    printk("major is %d \r\n", dev1.major); //打印主设备号
    printk("minor is %d \r\n", dev1.minor); //打印次设备号
     /*2 初始化cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_fops);

    /*3 添加一个cdev,完成字符设备注册到内核*/
   ret =  cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if(ret<0)
    {
        goto  err_chr_add;
    }
    /*4 创建类*/
  dev1. class = class_create(THIS_MODULE, "test");
    if(IS_ERR(dev1.class))
    {
        ret=PTR_ERR(dev1.class);
        goto err_class_create;
    }
    /*5  创建设备*/
  dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if(IS_ERR(dev1.device))
    {
        ret=PTR_ERR(dev1.device);
        goto err_device_create;
    }
/*本实验重点*****/
    dev1.vir_gpio_dr=ioremap(GPIO_DR,4);  //将物理地址转化为虚拟地址
    if(IS_ERR(dev1.vir_gpio_dr))
    {
        ret=PTR_ERR(dev1.vir_gpio_dr);  //PTR_ERR()来返回错误代码
        goto err_ioremap;
    }


return 0;

err_ioremap:
        iounmap(dev1.vir_gpio_dr);

 err_device_create:
        class_destroy(dev1.class);                 //删除类

err_class_create:
       cdev_del(&dev1.cdev_test);                 //删除cdev

err_chr_add:
        unregister_chrdev_region(dev1.dev_num, 1); //注销设备号

err_chrdev:
        return ret;
}




static void __exit chr_fops_exit(void) //驱动出口函数
{
    /*注销字符设备*/
    unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
    cdev_del(&dev1.cdev_test);                 //删除cdev
    device_destroy(dev1.class, dev1.dev_num);       //删除设备
    class_destroy(dev1.class);                 //删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

使用platform:

把注册字符设备的内容放到probe中,probe才是真正的入口函数

device.c:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>

struct resource my_led_resource[] = {
    [0] = {
        .start = 0xFDD6000,
        .end = 0xFDD6004,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 13,
        .end = 13,
        .flags = IORESOURCE_IRQ,
    }
};

void	my_dev_release(struct device *dev)
{
    printk("this is dev_release\n");
}

struct platform_device platform_led = {
    .name = "my_led",
    .id = -1,
    .resource = my_led_resource,  //resource中存的是device的信息(寄存器地址/中断号等内容)
    .num_resources = ARRAY_SIZE(my_led_resource),
    .dev = {
        .release = my_dev_release,  //dev中必须包含release函数
    },
};
static int platform_device_init(void)
{
    platform_device_register(&platform_led);
}
void platform_device_exit(void)
{
    platform_device_unregister(&platform_led);
}
module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("GPL");

driver.c:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>


struct device_test{
   
    dev_t dev_num;  //设备号
     int major ;  //主设备号
    int minor ;  //次设备号
    struct cdev cdev_test; // cdev
    struct class *class;   //类
    struct device *device; //设备
    char kbuf[32];
    unsigned int *vir_gpio_dr;
};

struct  device_test dev1;  


/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data=&dev1;//设置私有数据
    printk("This is cdev_test_open\r\n");

    return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
     struct device_test *test_dev=(struct device_test *)file->private_data;

    if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
    {
        printk("copy_from_user error\r\n");
        return -1;
    }
    if(test_dev->kbuf[0]==1){   //如果应用层传入的数据是1,则打开灯
            *(test_dev->vir_gpio_dr) = 0x8000c040;   //设置数据寄存器的地址
              printk("test_dev->kbuf [0]  is %d\n",test_dev->kbuf[0]);  //打印传入的数据
    }
    else if(test_dev->kbuf[0]==0)  //如果应用层传入的数据是0,则关闭灯
    {
            *(test_dev->vir_gpio_dr) = 0x80004040; //设置数据寄存器的地址
            printk("test_dev->kbuf [0]  is %d\n",test_dev->kbuf[0]); //打印传入的数据
    }
    return 0;
}

/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    struct device_test *test_dev=(struct device_test *)file->private_data;
    
    if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
    {
        printk("copy_to_user error\r\n");
        return -1;
    }

    printk("This is cdev_test_read\r\n");
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release\r\n");
    return 0;
}

/*设备操作函数*/
struct file_operations cdev_test_fops = {
    .owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = cdev_test_open, //将open字段指向chrdev_open(...)函数
    .read = cdev_test_read, //将open字段指向chrdev_read(...)函数
    .write = cdev_test_write, //将open字段指向chrdev_write(...)函数
    .release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};

struct resource* myresource;
int my_probe(struct platform_device *dev)
{
    int ret;
    //得到设备信息
    myresource = platform_get_resource(dev,IORESOURCE_MEM,0);
    int GPIO_DR = myresource->start;

    /*注册字符设备驱动*/
    /*1 创建设备号*/
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
    if (ret < 0)
    {
       goto err_chrdev;
    }
    printk("alloc_chrdev_region is ok\n");

    dev1.major = MAJOR(dev1.dev_num); //获取主设备号
    dev1.minor = MINOR(dev1.dev_num); //获取次设备号

    printk("major is %d \r\n", dev1.major); //打印主设备号
    printk("minor is %d \r\n", dev1.minor); //打印次设备号
     /*2 初始化cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_fops);

    /*3 添加一个cdev,完成字符设备注册到内核*/
   ret =  cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if(ret<0)
    {
        goto  err_chr_add;
    }
    /*4 创建类*/
  dev1. class = class_create(THIS_MODULE, "test");
    if(IS_ERR(dev1.class))
    {
        ret=PTR_ERR(dev1.class);
        goto err_class_create;
    }
    /*5  创建设备*/
  dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if(IS_ERR(dev1.device))
    {
        ret=PTR_ERR(dev1.device);
        goto err_device_create;
    }
/*本实验重点*****/
    dev1.vir_gpio_dr=ioremap(GPIO_DR,4);  //将物理地址转化为虚拟地址
    if(IS_ERR(dev1.vir_gpio_dr))
    {
        ret=PTR_ERR(dev1.vir_gpio_dr);  //PTR_ERR()来返回错误代码
        goto err_ioremap;
    }

return 0;

err_ioremap:
        iounmap(dev1.vir_gpio_dr);

 err_device_create:
        class_destroy(dev1.class);                 //删除类

err_class_create:
       cdev_del(&dev1.cdev_test);                 //删除cdev

err_chr_add:
        unregister_chrdev_region(dev1.dev_num, 1); //注销设备号

err_chrdev:
        return ret;
}
int my_remove(struct platform_device *dev)
{

}
//使用struct platform_device_id要包含头文件mod_devicetable.h
struct platform_device_id my_id_table = {
    .name = "my_led"
};
struct platform_driver my_platform_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_led",
        .owner = THIS_MODULE,
    },
    .id_table = &my_id_table,   //driver和id_table都是声明与device匹配的name的,id_table的优先级更高,相当于大名
};

static int platform_driver_init(void)
{
    platform_driver_register(&my_platform_driver);
}
void platform_driver_exit(void)
{
    /*注销字符设备*/
    unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
    cdev_del(&dev1.cdev_test);                 //删除cdev
    device_destroy(dev1.class, dev1.dev_num);       //删除设备
    class_destroy(dev1.class);                 //删除类

    platform_driver_unregister(&my_platform_driver);
}
module_init(platform_driver_init);
module_exit(platform_driver_exit);

MODULE_LICENSE("GPL");

5.总结

1. platform 是什么

platform 是 Linux 中的一种虚拟总线机制,主要用来管理 SoC 内部外设 或 板级固定设备,比如 GPIO、LED、UART、PWM 等。

2. 为什么需要 platform

这类设备通常不在真实物理总线上,不能像 USB、PCI 那样自动枚举,所以需要 Linux 用一种统一方式来描述设备并匹配驱动。

3. 两个核心对象

  • platform_device:描述设备本身和资源
  • platform_driver:描述驱动如何初始化设备

4. 匹配成功后做什么

当 platform_device 和 platform_driver 匹配成功后,内核会调用驱动里的:

cpp 复制代码
probe()

这个函数是平台驱动最核心的入口。

5. 匹配方式

现在最常见的是设备树 compatible 匹配:

  • 设备树写 compatible
  • 驱动里写 of_match_table

匹配成功后触发 probe()。

6. probe 里一般做什么

在 probe() 中通常完成:

  • 获取寄存器资源
  • 获取 GPIO / IRQ / 时钟
  • 初始化硬件
  • 注册字符设备或 miscdevice

7. platform 和字符设备的关系

它们不是二选一:

  • platform 负责底层资源获取和设备初始化
  • 字符设备负责给用户态提供 /dev/xxx 接口

很多驱动都是:platform + 字符设备

相关推荐
Deitymoon2 小时前
linux——TCP多进程并发服务器
linux·服务器·tcp/ip
网络安全许木2 小时前
自学渗透测试第15天(基础复习与漏洞原理入门)
linux·网络安全·渗透测试·kali linux
Hello World . .2 小时前
linux驱动编程2 : uboot、Linux内核、rootfs来源及制作流程
linux·运维·服务器
啦啦啦_99992 小时前
1. Linux常用命令
linux·运维·服务器
大白菜和MySQL2 小时前
openEuler-20.03-LTS系统 nextcloud网盘搭建
linux
Harvy_没救了2 小时前
【Linux】Shell指令中的变量
linux·运维·服务器
Deitymoon2 小时前
linux——TCP多线程并发服务器
linux·服务器·tcp/ip
senijusene3 小时前
IMX6ULL Linux 驱动开发流程:从环境搭建到系统启动与内核编译
linux·运维·驱动开发
格林威3 小时前
AI视觉项目部署:Docker 部署视觉服务可行性分析
linux·运维·人工智能·数码相机·docker·容器·工业相机