03. platform设备端

一、什么是platform_device

platform_device 是 Linux 内核中用来描述一个 Platform 设备的结构体。

它的作用是:

  • 告诉内核"这个硬件有什么资源"(寄存器地址、中断号、DMA 通道等)
  • 作为设备和驱动之间的"信息载体"
  • 在设备树和驱动之间搭建桥梁

二、platform_device结构体详解

platform_device 结构体定义在 TaishanPi-3-Linux/kernel-6.1/include/linux/platform_device.h 中:

struct platform_device {
    const char *name;                          // 设备名称
    int id;                                    // 设备ID(用于区分同名设备)
    bool id_auto;                              // ID是否自动分配
    struct device dev;                         // 内嵌的通用设备结构体
    u64 platform_dma_mask;                     // DMA地址掩码
    struct device_dma_parameters dma_parms;    // DMA参数
    u32 num_resources;                         // 资源数量
    struct resource *resource;                 // 资源数组指针
    const struct platform_device_id *id_entry; // ID匹配表条目
    const char *driver_override;               // 强制匹配的驱动名
    struct mfd_cell *mfd_cell;                 // MFD设备指针
    struct pdev_archdata archdata;             // 架构相关数据
};

特别注意一下核心参数:

  • name:设备名称
  • id:设备ID
  • dev:通用设备结构体(包含 of_node 指向设备树)
  • num_resources 和 resource:硬件资源数组

1、name(设备名称)

设备的名称,通常从设备树节点名提取。

mychardev@0 {
    compatible = "lckfb,mychardev";
    // ...
};

内核会创建一个 platform_device,其 name = "mychardev"。

2、id(设备ID)

用于区分同名设备,通常从设备树节点地址后缀提取。

mychardev@0 {
    // 第一个 mychardev 设备
};

mychardev@1 {
    // 第二个 mychardev 设备
};

内核会创建两个 platform_device:

  • 第一个:name = "mychardev",id = 0
  • 第二个:name = "mychardev",id = 1

如果没有 @ 地址后缀,id 默认为 -1。

3、id_auto(ID自动分配标志)

当 id_auto = true 时,内核会自动为设备分配一个唯一的 ID。

使用场景:

  • 手动创建 platform_device 时,不想手动指定 ID
  • 让内核自动管理设备ID

我们使用设备树时,通常不需要关心这个字段。

4、dev.of_node

dev.of_node 指向设备树节点,驱动通过它读取设备树属性。

// 驱动中读取设备树属性
u32 dev_id;
of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id);

5、资源数组

resource 数组用于描述设备的硬件资源(寄存器地址、中断号、DMA 通道等)。

每个资源由 struct resource 结构体描述:

struct resource {
    resource_size_t start;      /* 资源起始地址/编号 */
    resource_size_t end;        /* 资源结束地址/编号(包含) */
    const char *name;       /* 资源名称 */
    unsigned long flags;        /* 资源类型和属性标志 */
    unsigned long desc;     /* 资源描述符 */
    struct resource *parent;    /* 父资源(所属的更大范围) */
    struct resource *sibling;   /* 兄弟资源(同级的下一个) */
    struct resource *child;     /* 子资源(嵌套的子范围) */
};

Flags - 主要资源类型:

标志值含义
IORESOURCE_IO0x00000100IO 端口资源(x86)
IORESOURCE_MEM0x00000200内存映射资源
IORESOURCE_IRQ0x00000400中断资源
IORESOURCE_DMA0x00000800DMA 通道
IORESOURCE_BUS0x00001000总线地址空间

Flags - 资源属性:

标志含义
IORESOURCE_PREFETCH可预取(内存资源)
IORESOURCE_READONLY只读
IORESOURCE_CACHEABLE可缓存
IORESOURCE_BUSY资源已被占用
IORESOURCE_EXCLUSIVE独占访问

可以有多个标志组合:

struct resource mem = {
    .flags = IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_CACHEABLE,
};

三、设备树转换

1、设备树节点

我们创建以下的设备树节点

/ {
    mychardev@0 {
        compatible = "lckfb,mychardev";
        reg = <0x12340000 0x1000>;       // 寄存器地址
        interrupts = <56>;               // 中断号
        dev-id = <0xFFA8>;               // 自定义属性
        status = "okay";
    };
};

2、内核自动创建

内核自动创建 platform_device 结构体声明。

以下都是内核自动生成的

struct platform_device my_pdev = {
    .name = "mychardev",
    .id = 0,
    .num_resources = 2,
    .resource = {
        // reg 属性 → 寄存器资源
        {
            .start = 0x12340000,
            .end   = 0x12340FFF,
            .flags = IORESOURCE_MEM,
        },
        // interrupts 属性 → 中断资源
        {
            .start = 56,
            .end   = 56,
            .flags = IORESOURCE_IRQ,
        },
    },
    .dev. of_node = &mychardev_node,  // 指向设备树节点
};
设备树属性转换成的资源类型
regIORESOURCE_MEM
interruptsIORESOURCE_IRQ
dev-id 等自定义属性不转换,需用 of_property_read_*() 读取

四、驱动如何获取设备资源

1、获取寄存器地址

static int my_probe(struct platform_device *pdev)
{
    struct resource *res;

    // 获取第0个内存资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        printk(KERN_ERR "获取寄存器资源失败!\n");
        return -ENODEV;
    }

    return 0;
}

2、获取中断号

static int my_probe(struct platform_device *pdev)
{
    int irq;

    // 获取第0个中断资源
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        printk(KERN_ERR "获取中断号失败!\n");
        return irq;
    }

    return 0;
}

3、获取自定义属性

我在下面的例子中选择获取名为 dev-id 的值

static int my_probe(struct platform_device *pdev)
{
    u32 dev_id;

    // 读取 dev-id 属性
    if (of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id)) {
        printk(KERN_ERR "读取 dev-id 失败!\n");
        return -EINVAL;
    }

    return 0;
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐