07-platform总线模型.md 39 KB


title: platform总线模型 tags: [Linux驱动, platform, 设备驱动分离, 总线设备驱动, 设备树, 嵌入式] created: 2026-09-17 updated: 2026-09-17 pdf_ref:

  • "正点原子I.MX6U嵌入式Linux驱动开发指南V2.0.1 第五十四章 platform设备驱动实验"
  • "正点原子I.MX6U嵌入式Linux驱动开发指南V2.0.1 第五十五章 设备树下的platform驱动编写" ---

platform总线模型

关联知识: [[02-嵌入式Linux内核基础/06-设备模型与驱动框架]] | [[03-Linux驱动开发核心/02-设备树语法与实战]]


一、框架概述

1.1 为什么需要platform总线

Linux系统是一个成熟、庞大、复杂的操作系统,代码的重用性非常重要。驱动程序占用了Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码增加,内核文件数量将庞大到无法接受。

传统驱动的痛点:

假设有三个平台A、B、C,每个平台都有MPU6050这个I2C传感器。按传统方式,每个平台都要写一个MPU6050驱动,导致大量重复代码:

传统方式:
A平台I2C主机驱动 + MPU6050驱动
B平台I2C主机驱动 + MPU6050驱动
C平台I2C主机驱动 + MPU6050驱动

改进方案——驱动分离:

将主机驱动和设备驱动分隔开来,每个平台的I2C控制器提供统一接口,每个设备只提供一个驱动程序:

改进后:
A平台I2C主机驱动 ─┐
B平台I2C主机驱动 ─┼── 统一接口API ── MPU6050驱动
C平台I2C主机驱动 ─┘

驱动分离的核心思想:

  • 驱动只负责驱动逻辑
  • 设备只负责提供设备信息(寄存器地址、中断号等)
  • 通过总线进行匹配,匹配成功后调用probe函数

这就是Linux中的总线(bus)、驱动(driver)和设备(device)模型。

1.2 platform总线架构

SOC中有些外设(如GPIO、定时器等)没有物理总线概念,但又要使用总线-驱动-设备模型。为解决此问题,Linux提出了platform虚拟总线

graph TB
    subgraph "platform总线模型"
        Bus["platform_bus<br/>(虚拟总线)"]

        subgraph "设备侧"
            D1["platform_device<br/>name: xxx-gpio<br/>resource: 寄存器/中断"]
            D2["platform_device<br/>name: yyy-i2c<br/>resource: 寄存器/中断"]
            D3["设备树节点<br/>compatible = xxx-gpio"]
        end

        subgraph "驱动侧"
            DR1["platform_driver<br/>name: xxx<br/>probe: xxx_probe"]
            DR2["platform_driver<br/>name: yyy<br/>probe: yyy_probe"]
        end

        D1 -->|"匹配(name)"| Bus
        D2 -->|"匹配(name)"| Bus
        D3 -->|"匹配(compatible)"| Bus
        Bus -->|"匹配(name)"| DR1
        Bus -->|"匹配(name)"| DR2
    end

    Bus -->|"调用probe()"| DR1
    Bus -->|"调用probe()"| DR2

关键组件:

  • platform_bus_type: platform总线的bus_type实例,定义在drivers/base/platform.c
  • platform_device: 描述设备信息(寄存器地址、中断号等)
  • platform_driver: 实现驱动逻辑,提供probe/remove函数

1.3 platform_device vs platform_driver

特性 platform_device platform_driver
作用 描述设备硬件信息 实现驱动逻辑
定义位置 include/linux/platform_device.h include/linux/platform_device.h
关键成员 name, resource, num_resources probe, remove, driver, id_table
注册函数 platform_device_register() platform_driver_register()
设备树方式 无需手动编写,内核自动解析 需实现of_match_table
无设备树方式 需手动定义并注册 需定义id_table或name匹配

1.4 总线匹配机制

platform总线的匹配函数platform_match()定义在drivers/base/platform.c中,匹配优先级如下:

匹配顺序(从高到低):
1. driver_override强制匹配 → 直接比较名字
2. OF设备树匹配 → compatible属性匹配
3. ACPI匹配 → ACPI表匹配
4. id_table匹配 → platform_device_id数组匹配
5. name匹配 → 直接比较name字段

二、核心数据结构

2.1 struct platform_device详解

/* 文件: include/linux/platform_device.h */
struct platform_device {
    const char *name;           /* 设备名字,用于与驱动name字段匹配 */
    int id;                     /* 设备ID,-1表示自动分配 */
    bool id_auto;               /* 是否自动分配ID */
    struct device dev;          /* 内嵌的device结构体 */
    u32 num_resources;          /* 资源数量 */
    struct resource *resource;  /* 资源数组(寄存器、中断等) */

    const struct platform_device_id *id_entry;  /* id_table匹配项 */
    char *driver_override;      /* 强制绑定的驱动名 */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata archdata;
};

关键字段说明:

字段 说明
name 设备名字,必须与驱动的name字段相同才能匹配
dev 内嵌的device结构体,包含设备的基础信息
resource 资源数组,描述寄存器地址、中断号等硬件信息
num_resources resource数组的元素个数
driver_override 强制绑定指定驱动,优先级最高

2.2 struct platform_driver详解

/* 文件: include/linux/platform_device.h */
struct platform_driver {
    int (*probe)(struct platform_device *);     /* 匹配成功后执行 */
    int (*remove)(struct platform_device *);    /* 卸载驱动时执行 */
    void (*shutdown)(struct platform_device *); /* 关机时执行 */
    int (*suspend)(struct platform_device *, pm_message_t state); /* 挂起 */
    int (*resume)(struct platform_device *);    /* 恢复 */
    struct device_driver driver;                /* 基类device_driver */
    const struct platform_device_id *id_table;  /* id_table匹配表 */
    bool prevent_deferred_probe;                /* 阻止延迟探测 */
};

关键字段说明:

字段 说明
probe 驱动与设备匹配成功后自动调用,是驱动的核心入口
remove 卸载驱动时调用,用于释放资源
driver 基类device_driver,包含name、of_match_table等
id_table 用于传统name匹配方式

2.3 struct device_driver详解

/* 文件: include/linux/device.h */
struct device_driver {
    const char *name;                           /* 驱动名字 */
    struct bus_type *bus;                       /* 所属总线 */
    struct module *owner;                       /* 模块拥有者 */
    const char *mod_name;                       /* 内建模块名 */

    bool suppress_bind_attrs;                   /* 禁用sysfs绑定/解绑 */

    const struct of_device_id *of_match_table;  /* 设备树匹配表 */
    const struct acpi_device_id *acpi_match_table; /* ACPI匹配表 */

    int (*probe) (struct device *dev);          /* 探测函数 */
    int (*remove) (struct device *dev);         /* 移除函数 */
    void (*shutdown) (struct device *dev);      /* 关机函数 */
    int (*suspend) (struct device *dev, pm_message_t state); /* 挂起 */
    int (*resume) (struct device *dev);         /* 恢复 */
    const struct attribute_group **groups;       /* 属性组 */

    const struct dev_pm_ops *pm;                /* 电源管理操作 */
    struct driver_private *p;                   /* 驱动私有数据 */
};

2.4 struct of_device_id详解

/* 文件: include/linux/mod_devicetable.h */
struct of_device_id {
    char name[32];          /* 设备名(较少使用) */
    char type[32];          /* 设备类型(较少使用) */
    char compatible[128];   /* 兼容属性,设备树匹配的关键字段 */
    const void *data;       /* 私有数据指针 */
};

compatible字段是最关键的,设备树中每个设备节点的compatible属性会与of_match_table中每个项目的compatible成员进行比较。

2.5 struct resource详解

/* 文件: include/linux/ioport.h */
struct resource {
    resource_size_t start;      /* 资源起始地址 */
    resource_size_t end;        /* 资源结束地址 */
    const char *name;           /* 资源名字 */
    unsigned long flags;        /* 资源类型标志 */
    struct resource *parent, *sibling, *child;  /* 资源树 */
};

资源类型标志:

/* 文件: include/linux/ioport.h */
#define IORESOURCE_BITS      0x000000ff  /* 总线特定位 */

#define IORESOURCE_TYPE_BITS 0x00001f00  /* 资源类型掩码 */
#define IORESOURCE_IO        0x00000100  /* PCI/ISA I/O端口 */
#define IORESOURCE_MEM       0x00000200  /* 内存资源 */
#define IORESOURCE_REG       0x00000300  /* 寄存器偏移 */
#define IORESOURCE_IRQ       0x00000400  /* 中断资源 */
#define IORESOURCE_DMA       0x00000800  /* DMA资源 */
#define IORESOURCE_BUS       0x00001000  /* 总线资源 */

三、设备树匹配

3.1 compatible属性匹配

在设备树中,设备节点通过compatible属性描述设备信息:

/* IMX6ULL LED设备节点示例 */
gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-gpioled";  /* 匹配驱动的关键属性 */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

3.2 of_match_table使用

驱动中通过of_match_table声明兼容的设备:

/* 匹配表定义 */
static const struct of_device_id leds_of_match[] = {
    { .compatible = "atkalpha-gpioled" },  /* 兼容属性 */
    { /* Sentinel - 最后一个必须为空 */ }
};

/* 声明设备表,使内核能识别 */
MODULE_DEVICE_TABLE(of, leds_of_match);

/* platform_driver中引用匹配表 */
static struct platform_driver leds_platform_driver = {
    .driver = {
        .name = "imx6ul-led",             /* 传统name匹配 */
        .of_match_table = leds_of_match,   /* 设备树匹配 */
    },
    .probe = leds_probe,
    .remove = leds_remove,
};

3.3 匹配流程图

flowchart TD
    A["驱动/设备注册到platform总线"] --> B{"匹配函数 platform_match()"}

    B --> C{"1. driver_override<br/>是否设置?"}
    C -->|"是"| D["强制匹配driver_override指定的驱动"]
    C -->|"否"| E{"2. OF设备树匹配<br/>of_driver_match_device()"}

    E -->|"设备有compatible<br/>驱动有of_match_table"| F["比较compatible属性"]
    F -->|"匹配成功"| G["调用probe()"]
    F -->|"不匹配"| H{"3. ACPI匹配"}

    E -->|"无设备树支持"| H
    H -->|"ACPI系统"| I["ACPI表匹配"]
    H -->|"非ACPI系统"| J{"4. id_table匹配<br/>platform_match_id()"}

    J -->|"驱动有id_table"| K["遍历id_table比较name"]
    K -->|"匹配成功"| G
    K -->|"不匹配"| L{"5. name匹配<br/>strcmp(pdev->name, drv->name)"}

    J -->|"驱动无id_table"| L
    L -->|"name相同"| G
    L -->|"name不同"| M["匹配失败"]

    G --> N["probe函数执行<br/>驱动正式工作"]

四、驱动框架模板

4.1 platform_driver骨架代码(无设备树方式)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT  1           /* 设备号长度 */
#define LEDDEV_NAME "platled"  /* 设备名字 */
#define LEDOFF      0
#define LEDON       1

/* 设备结构体 */
struct leddev_dev {
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
};

struct leddev_dev leddev;   /* led设备 */

/* LED打开/关闭 */
void led0_switch(u8 sta)
{
    /* 根据sta控制LED */
}

/* 打开设备 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev; /* 设置私有数据 */
    return 0;
}

/* 向设备写数据 */
static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        return -EFAULT;
    }

    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led0_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led0_switch(LEDOFF);
    }
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/*
 * probe函数 - 驱动与设备匹配成功后执行
 */
static int xxx_probe(struct platform_device *dev)
{
    /* 初始化LED */
    /* 注册字符设备驱动 */
    cdev_init(&leddev.cdev, &led_fops);
    /* 其他初始化工作 */
    return 0;
}

/*
 * remove函数 - 卸载platform驱动时执行
 */
static int xxx_remove(struct platform_device *dev)
{
    cdev_del(&leddev.cdev); /* 删除cdev */
    /* 释放资源 */
    return 0;
}

/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
    { .compatible = "xxx-gpio" },
    { /* Sentinel */ }
};

/* platform平台驱动结构体 */
static struct platform_driver xxx_driver = {
    .driver = {
        .name = "xxx",
        .of_match_table = xxx_of_match,
    },
    .probe = xxx_probe,
    .remove = xxx_remove,
};

/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
    return platform_driver_register(&xxx_driver);
}

/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
    platform_driver_unregister(&xxx_driver);
}

module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

4.2 probe函数详解

probe函数是platform驱动的核心,在设备与驱动匹配成功后自动调用。主要完成以下工作:

static int xxx_probe(struct platform_device *dev)
{
    /* 1. 获取资源 */
    struct resource *mem;
    mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
    if (!mem) {
        dev_err(&dev->dev, "No MEM resource\n");
        return -ENXIO;
    }

    /* 2. 内存映射 */
    void __iomem *base;
    base = devm_ioremap_resource(&dev->dev, mem);
    if (IS_ERR(base)) {
        return PTR_ERR(base);
    }

    /* 3. 获取中断号 */
    int irq;
    irq = platform_get_irq(dev, 0);
    if (irq < 0) {
        dev_err(&dev->dev, "No IRQ resource\n");
        return irq;
    }

    /* 4. 初始化硬件 */
    /* 5. 注册字符设备驱动 */
    /* 6. 创建设备节点 */

    dev_info(&dev->dev, "Driver probed successfully\n");
    return 0;
}

4.3 remove函数详解

remove函数在卸载驱动时执行,负责释放所有资源:

static int xxx_remove(struct platform_device *dev)
{
    /* 1. 释放内存映射 */
    iounmap(base);

    /* 2. 删除cdev */
    cdev_del(&leddev.cdev);

    /* 3. 注销设备号 */
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);

    /* 4. 删除设备 */
    device_destroy(leddev.class, leddev.devid);

    /* 5. 删除类 */
    class_destroy(leddev.class);

    dev_info(&dev->dev, "Driver removed\n");
    return 0;
}

4.4 模块加载/卸载

/* 驱动模块加载 - 向内核注册platform驱动 */
static int __init xxxdriver_init(void)
{
    return platform_driver_register(&xxx_driver);
}

/* 驱动模块卸载 - 从内核注销platform驱动 */
static void __exit xxxdriver_exit(void)
{
    platform_driver_unregister(&xxx_driver);
}

module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

五、完整源码分析

5.1 LED platform设备文件(leddevice.c)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 寄存器地址定义 */
#define CCM_CCGR1_BASE          (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define GPIO1_DR_BASE           (0X0209C000)
#define GPIO1_GDIR_BASE         (0X0209C004)
#define REGISTER_LENGTH         4

/* 释放platform设备模块时执行 */
static void led_release(struct device *dev)
{
    printk("led device released!\r\n");
}

/* 设备资源信息 - LED0使用的所有寄存器 */
static struct resource led_resources[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end   = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = SW_MUX_GPIO1_IO03_BASE,
        .end   = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = SW_PAD_GPIO1_IO03_BASE,
        .end   = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [3] = {
        .start = GPIO1_DR_BASE,
        .end   = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [4] = {
        .start = GPIO1_GDIR_BASE,
        .end   = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
};

/* platform设备结构体 */
static struct platform_device leddevice = {
    .name = "imx6ul-led",
    .id   = -1,
    .dev  = {
        .release = &led_release,
    },
    .num_resources = ARRAY_SIZE(led_resources),
    .resource      = led_resources,
};

/* 设备模块加载 */
static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}

/* 设备模块注销 */
static void __exit leddevice_exit(void)
{
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

逐行解释:

行号 说明
37-42 定义LED使用的寄存器物理地址
48-51 设备释放回调函数,设备移除时调用
56-82 led_resources数组,描述5个寄存器的内存资源
88-96 platform设备结构体,name必须与驱动name匹配
103-106 模块加载函数,调用platform_device_register()注册设备
113-116 模块卸载函数,调用platform_device_unregister()注销设备

5.2 LED platform驱动文件(leddriver.c)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT  1           /* 设备号长度 */
#define LEDDEV_NAME "platled"  /* 设备名字 */
#define LEDOFF      0
#define LEDON       1

/* 寄存器虚拟地址 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* leddev设备结构体 */
struct leddev_dev {
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
};

struct leddev_dev leddev;   /* led设备 */

/* LED打开/关闭 */
void led0_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    } else if (sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

/* 打开设备 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;
    return 0;
}

/* 向设备写数据 */
static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        return -EFAULT;
    }

    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led0_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led0_switch(LEDOFF);
    }
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .write = led_write,
};

/*
 * probe函数 - 当驱动与设备匹配成功后执行
 */
static int led_probe(struct platform_device *dev)
{
    int i = 0;
    int ressize[5];
    u32 val = 0;
    struct resource *ledsource[5];

    printk("led driver and device has matched!\r\n");

    /* 1、获取资源 */
    for (i = 0; i < 5; i++) {
        ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
        if (!ledsource[i]) {
            dev_err(&dev->dev, "No MEM resource for always on\n");
            return -ENXIO;
        }
        ressize[i] = resource_size(ledsource[i]);
    }

    /* 2、初始化LED */
    /* 寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
    SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
    SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
    GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
    GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);

    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);
    val |= (3 << 26);
    writel(val, IMX6U_CCM_CCGR1);

    /* 设置GPIO1_IO03复用功能 */
    writel(5, SW_MUX_GPIO1_IO03);
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /* 设置GPIO1_IO03为输出功能 */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3);
    val |= (1 << 3);
    writel(val, GPIO1_GDIR);

    /* 默认关闭LED1 */
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /* 3、注册字符设备驱动 */
    if (leddev.major) {
        leddev.devid = MKDEV(leddev.major, 0);
        register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
    } else {
        alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
        leddev.major = MAJOR(leddev.devid);
    }

    /* 4、初始化cdev */
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev, &led_fops);

    /* 5、添加一个cdev */
    cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

    /* 6、创建类 */
    leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    if (IS_ERR(leddev.class)) {
        return PTR_ERR(leddev.class);
    }

    /* 7、创建设备 */
    leddev.device = device_create(leddev.class, NULL, leddev.devid,
                                  NULL, LEDDEV_NAME);
    if (IS_ERR(leddev.device)) {
        return PTR_ERR(leddev.device);
    }

    return 0;
}

/*
 * remove函数 - 卸载platform驱动时执行
 */
static int led_remove(struct platform_device *dev)
{
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    cdev_del(&leddev.cdev);
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);
    return 0;
}

/* platform驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",
    },
    .probe  = led_probe,
    .remove = led_remove,
};

/* 驱动模块加载 */
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/* 驱动模块卸载 */
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

逐行解释:

行号 说明
40-44 定义寄存器虚拟地址指针
47-55 设备结构体,包含设备号、cdev、类、设备等
62-74 LED开关函数,通过操作GPIO数据寄存器控制LED
83-87 open函数,设置私有数据指针
97-115 write函数,从用户空间读取数据控制LED
130-206 probe函数,匹配成功后执行:获取资源→映射寄存器→初始化GPIO→注册字符设备
213-226 remove函数,卸载时释放所有资源
229-235 platform_driver结构体,name必须与设备name匹配
242-245 模块加载函数,调用platform_driver_register()
252-255 模块卸载函数,调用platform_driver_unregister()

5.3 设备树下的platform驱动(设备树匹配方式)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT  1
#define LEDDEV_NAME "dtsplatled"
#define LEDOFF      0
#define LEDON       1

struct leddev_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    struct device_node *node;  /* LED设备节点 */
    int led0;                  /* LED灯GPIO标号 */
};

struct leddev_dev leddev;

void led0_switch(u8 sta)
{
    if (sta == LEDON)
        gpio_set_value(leddev.led0, 0);
    else if (sta == LEDOFF)
        gpio_set_value(leddev.led0, 1);
}

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[2];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led0_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led0_switch(LEDOFF);
    }
    return 0;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .write = led_write,
};

/*
 * probe函数 - 设备树匹配成功后执行
 */
static int led_probe(struct platform_device *dev)
{
    printk("led driver and device was matched!\r\n");

    /* 1、设置设备号 */
    if (leddev.major) {
        leddev.devid = MKDEV(leddev.major, 0);
        register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
    } else {
        alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
        leddev.major = MAJOR(leddev.devid);
    }

    /* 2、注册设备 */
    cdev_init(&leddev.cdev, &led_fops);
    cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

    /* 3、创建类 */
    leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    if (IS_ERR(leddev.class)) {
        return PTR_ERR(leddev.class);
    }

    /* 4、创建设备 */
    leddev.device = device_create(leddev.class, NULL, leddev.devid,
                                  NULL, LEDDEV_NAME);
    if (IS_ERR(leddev.device)) {
        return PTR_ERR(leddev.device);
    }

    /* 5、初始化IO */
    leddev.node = of_find_node_by_path("/gpioled");
    if (leddev.node == NULL) {
        printk("gpioled node nost find!\r\n");
        return -EINVAL;
    }

    leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
    if (leddev.led0 < 0) {
        printk("can't get led-gpio\r\n");
        return -EINVAL;
    }

    gpio_request(leddev.led0, "led0");
    gpio_direction_output(leddev.led0, 1);

    return 0;
}

static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.led0, 1);
    cdev_del(&leddev.cdev);
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);
    return 0;
}

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
    { .compatible = "atkalpha-gpioled" },
    { /* Sentinel */ }
};

/* platform驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",
        .of_match_table = led_of_match,
    },
    .probe  = led_probe,
    .remove = led_remove,
};

static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

与无设备树方式的区别:

特性 无设备树方式 设备树方式
设备信息 手动编写platform_device 设备树节点自动解析
匹配方式 name字段匹配 compatible属性匹配
获取资源 platform_get_resource() of_find_node_by_path() + of_get_named_gpio()
内存映射 ioremap() of_iomap()devm_ioremap_resource()
需要文件 leddevice.c + leddriver.c 仅leddriver.c

六、实验验证

6.1 设备树修改

在设备树中添加LED设备节点(以IMX6ULL为例):

/* 在根节点下添加 */
/ {
    gpioled {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "atkalpha-gpioled";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_led>;
        led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
        status = "okay";
    };
};

/* 在iomuxc节点下添加pinctrl */
&iomuxc {
    pinctrl_led: ledgrp {
        fsl,pins = <
            MX6ULL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
        >;
    };
};

6.2 驱动编译

Makefile:

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

CURRENT_PATH := $(shell pwd)

obj-m := leddriver.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译命令:

# 编译驱动模块
make -j32

# 编译测试APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp

6.3 加载测试

# 拷贝文件到开发板
scp leddriver.ko root@192.168.1.232:/lib/modules/4.1.15/
scp ledApp root@192.168.1.232:/lib/modules/4.1.15/

# 开发板上执行
cd /lib/modules/4.1.15/

# 第一次加载需要执行depmod
depmod

# 加载驱动模块
modprobe leddriver.ko

# 查看驱动是否加载成功
ls /sys/bus/platform/drivers/imx6ul-led/

# 查看设备是否加载成功
ls /sys/bus/platform/devices/

# 测试LED
./ledApp /dev/dtsplatled 1  # 打开LED
./ledApp /dev/dtsplatled 0  # 关闭LED

# 卸载驱动
rmmod leddriver.ko

验证要点:

  1. 驱动加载后终端输出"led driver and device was matched!"
  2. /sys/bus/platform/drivers/下存在imx6ul-led
  3. /sys/bus/platform/devices/下存在设备节点
  4. LED能正常开关

七、跨平台对比

特性 IMX6ULL STM32 (Linux) RK3568
平台总线 platform虚拟总线 platform虚拟总线 platform虚拟总线
设备树支持 完整支持 部分支持 完整支持
匹配方式 OF/name OF/name/ACPI OF/name
资源获取 platform_get_resource() platform_get_resource() platform_get_resource()
GPIO管理 of_get_named_gpio() of_get_named_gpio() gpiod_get()
时钟管理 devm_clk_get() devm_clk_get() devm_clk_get()
设备树示例 compatible = "atkalpha-gpioled" compatible = "st,stm32-led" compatible = "rockchip,gpio-led"

差异点:

  • IMX6ULL:NXP的Cortex-A7 SoC,设备树支持完善,GPIO用&gpio1引用
  • STM32:ST的Cortex-A/A7 SoC,部分型号设备树支持较弱
  • RK3568:Rockchip的Cortex-A55 SoC,推荐使用gpiod接口

八、面试精选

题目1:platform总线模型的核心思想是什么?为什么需要它?

考察点:驱动分离与分层

参考答案

platform总线模型的核心思想是驱动分离——将设备信息与驱动逻辑分离,通过总线进行匹配。

为什么需要:

  1. 代码重用:同一设备驱动可复用于不同平台,避免重复编写
  2. 可维护性:设备信息和驱动逻辑独立修改,互不影响
  3. 可扩展性:新增设备只需添加设备节点,无需修改驱动代码
  4. 统一模型:为无物理总线的外设(GPIO、定时器等)提供统一的总线-驱动-设备模型

架构组成

  • platform_bus_type:虚拟总线,负责匹配
  • platform_device:描述硬件信息(寄存器、中断)
  • platform_driver:实现驱动逻辑(probe/remove)

题目2:platform总线有几种匹配方式?优先级如何?

考察点:匹配机制

参考答案

platform_match()函数按以下优先级依次匹配:

优先级 匹配方式 说明
1 driver_override 强制绑定指定驱动,优先级最高
2 OF设备树匹配 比较compatible属性,最常用
3 ACPI匹配 用于ACPI系统
4 id_table匹配 比较platform_device_id数组
5 name匹配 直接比较name字段,最简单

代码位置drivers/base/platform.c中的platform_match()函数

实际应用:现代Linux驱动通常同时支持OF匹配和name匹配,以保证兼容性。


题目3:probe函数在什么时候执行?里面应该做什么?

考察点:驱动核心逻辑

参考答案

执行时机:当设备与驱动通过总线匹配成功后,platform_match()调用driver_probe_device(),最终执行probe()函数。

probe函数应该完成:

  1. 获取资源platform_get_resource()获取寄存器、中断等
  2. 内存映射ioremap()devm_ioremap_resource()
  3. 初始化硬件:配置寄存器、设置GPIO等
  4. 注册字符设备register_chrdev_region()cdev_init()cdev_add()
  5. 创建设备节点class_create()device_create()
  6. 申请中断request_irq()(如需要)

注意:probe函数中不能执行阻塞操作。


题目4:设备树方式下如何获取设备资源?

考察点:设备树API

参考答案

资源类型 API函数 示例
设备节点 of_find_node_by_path() of_find_node_by_path("/gpioled")
GPIO of_get_named_gpio() of_get_named_gpio(node, "led-gpio", 0)
寄存器 of_iomap() of_iomap(node, 0)
中断 platform_get_irq() platform_get_irq(dev, 0)
时钟 devm_clk_get() devm_clk_get(&dev->dev, "ipg")
属性值 of_property_read_u32() of_property_read_u32(node, "reg", &val)

推荐使用devm_系列函数,它们会在设备移除时自动释放资源。


题目5:platform驱动与字符设备驱动的关系是什么?

考察点:驱动模型理解

参考答案

关系:platform驱动是框架,字符设备驱动是实现

  • platform驱动:提供设备匹配机制,管理设备生命周期(probe/remove)
  • 字符设备驱动:实现具体的文件操作(open/read/write/close)

类比

platform驱动 = 外壳(管理框架)
字符设备驱动 = 内核(具体功能)

代码体现

static int xxx_probe(struct platform_device *dev) {
    /* platform框架的probe函数 */
    /* 在这里面实现字符设备驱动 */
    cdev_init(&xxxdev.cdev, &xxx_fops);  /* 字符设备 */
    cdev_add(&xxxdev.cdev, ...);
    class_create(...);
    device_create(...);
    return 0;
}

总结:platform驱动是字符设备驱动的一种封装形式,目的是实现驱动的分离与分层。字符设备驱动的核心逻辑仍然在probe函数中实现。