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 + 字符设备

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言