当前位置:首页>Linux>嵌入式Linux驱动开发 —— 从 DTS 到代码的桥梁与简单OF系列API

嵌入式Linux驱动开发 —— 从 DTS 到代码的桥梁与简单OF系列API

  • 2026-06-30 22:37:56
嵌入式Linux驱动开发 —— 从 DTS 到代码的桥梁与简单OF系列API

嵌入式Linux驱动开发 —— 从 DTS 到代码的桥梁与简单OF系列API

仓库已经开源!所有教程,主线内核移植,跑新版本imx-linux/uboot都在这里,或者一起来尝试跑7.0的Linux!欢迎各位大佬观摩!喜欢的话点个⭐!

仓库地址:https://github.com/Awesome-Embedded-Learning-Studio/imx-forge

静态网页:https://awesome-embedded-learning-studio.github.io/imx-forge/

前言:当设备树遇见驱动代码

前面我们聊了设备树的语法和编译原理,知道了 .dts 文件是如何被编译成 .dtb 然后被内核解析的。但说实话,这些只是"准备工作"。对于驱动开发者来说,真正的问题在于:我的驱动代码怎么去用这些设备树信息?

设备树里写着 reg = <0x020C406C 0x04>,但这只是个文本描述。驱动程序在运行时需要知道这个地址,需要把它映射成虚拟地址,然后才能去读写寄存器。中间缺了一个环节——需要有人在运行时去解析设备树,把那些 < > 里的数字提取出来,塞给 C 代码。

Linux 内核提供了这个环节,那就是一系列以 of_ 为前缀的 API 函数。你可以把它们理解为设备树和驱动代码之间的"翻译官"。

但这里有个历史遗留问题可能会困扰你:为什么叫 "OF" 而不是 "DT"?Device Tree 的缩写不是 DT 吗?这个问题的答案藏在设备树的历史里,我们稍后再说。现在先记住一点:当你看到 of_xxx() 这样的函数时,它们就是在操作设备树。

这一章我们会系统地介绍这些 API,看看它们是如何在实际驱动中使用的。我们还会拿 LED 驱动的代码做例子,看看那些在设备树里写的属性,是怎么一步步变成驱动里的寄存器地址的。


快速回顾:设备树的前世今生

在深入 API 之前,我们先快速过一遍设备树是怎么走到今天的。这段历史能帮你理解为什么内核里操作设备树的函数都叫 of_xxx(),以及设备树为什么被设计成现在这个样子。

从 PowerPC 到 ARM:一场被逼出来的变革

设备树最早不是 ARM 的发明。上世纪 90 年代,IBM 和苹果在 PowerPC 架构上制定了一个叫 Open Firmware 的固件标准,核心思想是让固件向操作系统提供一份完整的硬件描述,这样系统就不用为每块板子写专门的初始化代码了。设备树就是这个标准里定义的数据结构——用树状层次描述所有设备,每个节点包含寄存器地址、中断号、时钟频率等属性。

到了 2000 年代中后期,ARM 芯片爆发式增长,但 ARM Linux 处理硬件描述的方式极其原始:直接硬编码在 C 代码里。内核源码树里塞满了 arch/arm/mach-xxx 和 arch/arm/plat-xxx 目录,每个对应一种板子,重复率高达 90% 以上。维护成本高得离谱,代码膨胀到 arch/arm 的代码量比其他所有架构加起来还多。

2011 年,Linus Torvalds 终于爆发了:

"This whole ARM thing is a f*cking pain in the ass."

他明确拒绝继续合并这些垃圾代码。ARM 社区被迫改革,引入了 PowerPC 上已经成熟的设备树机制,经历了一个从可选到强制的演进过程。到了 2013 年左右,新的 ARM 板级代码几乎都使用了设备树;ARM64 更是从设计之初就强制要求设备树,不支持传统的板级 C 代码。

如今,设备树已成为 Linux 嵌入式领域描述硬件的通用机制,覆盖 ARM、ARM64、RISC-V、PowerPC、MIPS 等多个架构。

OF 命名的由来

因为设备树起源于 Open Firmware 标准,内核里操作设备树的函数就都叫 of_xxx()。后来 ARM 社区引入设备树时,为了复用已有的基础设施,也沿用了这个命名前缀。所以今天我们在 ARM Linux 里看到的设备树 API,依然叫 OF API,而不是 DT API。你可以把它理解为一种"历史遗产"——就像 C 语言的 printf 而不是 print

那么 OF 和设备树是什么关系呢?设备树是数据结构,OF API 是操作这个数据结构的一套函数。就像 C 语言里有 struct 和操作 struct 的函数一样,设备树是"数据",OF API 是"操作数据的工具"。

在 Linux 内核的源码里,你会看到这样的头文件:

  • include/linux/of.h
    :核心 OF API 定义
  • include/linux/of_address.h
    :地址映射相关函数
  • include/linux/of_gpio.h
    :GPIO 相关函数
  • include/linux/of_irq.h
    :中断相关函数

这些文件里定义的所有函数,都是我们这一章要讲的内容。


核心数据结构:device_node、property 和 resource

在讲具体的 API 之前,我们需要先了解一下内核是用什么数据结构来表示设备树的。毕竟 API 只是操作这些数据结构的工具,如果不了解数据结构本身,用起 API 来也是一头雾水。

struct device_node:节点的内核表示

struct device_node 是内核对设备树节点的描述。每个设备树节点在内核里都对应一个 device_node 结构体。这个结构体的定义在 include/linux/of.h 里,我们挑重点字段看:

structdevice_node {
constchar *name;        /* 节点名字,比如 "gpio" */
constchar *type;        /* 设备类型,取自 device_type 属性 */
    phandle phandle;         /* 节点的 phandle 值 */
constchar *full_name;   /* 节点的全路径名 */
structfwnode_handlefwnode;

structproperty *properties;/* 属性链表头 */
structproperty *deadprops;/* 已删除的属性 */

structdevice_node *parent;/* 父节点 */
structdevice_node *child;/* 子节点 */
structdevice_node *sibling;/* 兄弟节点 */

structkobjectkobj;
unsignedlong _flags;
void *data;
/* ... 更多平台特定字段 ... */
};

这个结构体设计得很巧妙。它不仅记录了节点的名字和类型,还通过 parentchild 和 sibling 三个指针把整棵树串了起来。这意味着你可以从任意一个节点出发,往上找父节点,往下找子节点,往旁边找兄弟节点——就像在真的树上爬一样。

properties 字段指向一个属性链表,所有的 property 结构体都挂在这个链表上。我们接下来看 property 结构体。

struct property:属性的内核表示

structproperty {
char *name;            /* 属性名字,比如 "reg" */
int length;            /* 属性值的字节长度 */
void *value;           /* 属性值,可以是任意数据 */
structproperty *next;/* 指向下一个属性 */
unsignedlong _flags;
unsignedint unique_id;
structbin_attributeattr;
};

这里最关键的是 value 字段。它是个 void *,可以指向任意类型的数据。这是因为设备树里的属性值可以是各种类型:可能是单个整数,可能是字符串,可能是整数数组,甚至可能是任意字节序列。

内核怎么知道 value 里存的是什么类型呢?答案是:不知道。内核只知道这是一坨字节,具体怎么解释,要看属性的名字和上下文。比如 status 属性通常被解释为字符串,reg 属性被解释为整数数组,而 compatible 属性被解释为字符串数组。

所以当我们用 API 读取属性时,需要明确告诉内核我们想要什么类型的数据。这就是为什么有 of_property_read_u32()of_property_read_string() 这样不同的函数。

struct resource:资源的统一描述

Linux 内核用 struct resource 来统一描述各种资源——不仅仅是内存映射 IO,还包括中断、DMA 通道等。这个结构体定义在 include/linux/ioport.h

structresource {
resource_size_t start;  /* 资源起始地址/号 */
resource_size_t end;    /* 资源结束地址/号 */
constchar *name;       /* 资源名称 */
unsignedlong flags;    /* 资源类型标志 */
structresource *parent, *sibling, *child;
};

flags 字段说明这是什么类型的资源:

  • IORESOURCE_MEM
    :内存映射 IO
  • IORESOURCE_IRQ
    :中断资源
  • IORESOURCE_IO
    :端口 IO(x86 特有)
  • IORESOURCE_DMA
    :DMA 通道

设备树里的 reg 属性可以通过 of_address_to_resource() 函数转换成 resource 结构体,这样驱动就可以用统一的方式来处理不同类型的资源了。


节点查找 API:如何在设备树中定位目标节点

有了数据结构基础,现在我们可以开始讲具体的 API 了。第一步是找到你要操作的节点。就像你想操作一个文件,得先找到它的路径一样,你想操作设备树里的某个节点,也得先定位到它。

内核提供了好几种查找节点的方法,适用于不同的场景。我们一个一个来看。

of_find_node_by_path:按路径查找

这是最直接的方法。如果你知道节点的完整路径,用这个函数最快:

struct device_node *of_find_node_by_path(constchar *path);

参数 path 是节点的完整路径,比如 "/imx_aes_led"。返回值是找到的节点指针,如果没找到就返回 NULL

这个函数在我们的 LED 驱动里用到了:

/* 从 /home/charliechen/imx-forge/driver/device_tree_try_03/alpha-board/led_hw.c */
staticconstchar* kIMX_AES_LED = "/imx_aes_led";

led.device_tree_node = of_find_node_by_path(kIMX_AES_LED);
if (led.device_tree_node == NULL) {
    pr_err("dtsled node can not found!\n");
return -EINVAL;
}

这里我们直接用路径 /imx_aes_led 去找节点。这个路径对应设备树里的定义:

/* 从 /home/charliechen/imx-forge/driver/device_tree/alpha-board/device_tree_try_03/imx6ull-aes-led.dts */
/ {
    imx_aes_led {
        
#address-cells = <1>;
        #size-cells = <1>;
        compatible = "atkalpha-led";
        status = "okay";
        reg = <...>;
    };
};

of_find_node_by_path() 的好处是简单直接,缺点是你要知道确切的路径。如果你只是想找某个类型的设备(比如所有的 GPIO 控制器),这个方法就不太方便了。

of_find_node_by_name:按节点名查找

struct device_node *of_find_node_by_name(struct device_node *from,
constchar *name)
;

这个函数按节点名查找。注意节点名不是 compatible 属性,而是节点本身的名字。比如节点 gpio1 { ... } 的名字就是 "gpio1"

from 参数指定从哪里开始找。如果传 NULL,就从根节点开始遍历整棵树。如果传一个具体的节点,就从那个节点之后继续找(这个设计允许你多次调用来遍历所有同名节点)。

这个函数在实际驱动里用得不多,因为节点名往往不够具体。同一个设备树上可能有很多叫 "gpio" 的节点,你很难确定找到的是哪一个。

of_find_compatible_node:按兼容性查找

这是驱动里最常用的查找函数:

struct device_node *of_find_compatible_node(struct device_node *from,
constchar *type,
constchar *compatible)
;

参数说明:

  • from
    :起始节点,NULL 表示从根开始
  • type
    device_type 属性值,可以传 NULL 表示不检查
  • compatible
    :要匹配的 compatible 属性字符串

这个函数会遍历设备树,找到第一个 compatible 属性包含指定字符串的节点。比如你可以用 "fsl,imx6ul-gpio" 来找 NXP 的 GPIO 控制器。

这里需要注意一点:compatible 属性可以包含多个字符串,用逗号分隔。of_find_compatible_node() 会检查所有这些字符串,只要有一个匹配就认为找到了。

of_find_matching_node_and_match:按匹配表查找

这是最强大的查找函数,它直接拿驱动里的 of_device_id 匹配表去过滤节点:

struct device_node *of_find_matching_node_and_match(
struct device_node *from,
conststruct of_device_id *matches,
conststruct of_device_id **match)
;

matches 参数就是驱动里的 .of_match_table,比如:

staticconststructof_device_idled_of_match[] = {
    { .compatible = "atkalpha-led", },
    { /* sentinel */ }
};

这个函数会遍历匹配表,找到第一个匹配的节点。match 是输出参数,告诉你具体匹配上了表里的哪一项。

在实际的 platform 驱动框架里,这个函数通常不需要你手动调用。驱动核心会自动帮你匹配。但如果你在写一些特殊逻辑(比如在驱动初始化时主动查找某个设备),这个函数就很有用了。


属性读取 API:如何从节点中提取信息

找到了节点,下一步就是读取它的属性。这是 OF API 的核心部分,也是驱动开发者用得最多的部分。

of_find_property:查找属性结构体

这是最底层的属性查找函数:

struct property *of_find_property(conststruct device_node *np,
constchar *name,
int *lenp)
;

参数说明:

  • np
    :设备节点
  • name
    :属性名
  • lenp
    :输出参数,返回属性值的字节长度

返回值是找到的 property 结构体指针,如果没找到就返回 NULL

这个函数返回的是原始的 property 结构体,你可以直接访问它的 value 字段。但 value 是 void * 类型,你需要自己解释它的内容。

我们的 LED 驱动里用这个函数读取了 compatible 属性:

structpropertyproper;

proper = of_find_property(led.device_tree_node, "compatible"NULL);
if (proper == NULL) {
    pr_err("compatible property find failed\n");
else {
    pr_info("compatible = %s\n", (char*)proper->value);
}

这里我们知道 compatible 属性的值是个字符串,所以直接把 value 强转成 char * 来打印。但这种方法并不安全,因为 compatible 实际上是个字符串数组,可能包含多个以 null 结尾的字符串。更好的做法是用专门的字符串读取函数,我们稍后讲。

of_property_read_string:读取字符串属性

intof_property_read_string(struct device_node *np,
constchar *propname,
constchar **out_string)
;

这个函数用于读取字符串类型的属性,比如 statusdevice_type 等。

参数说明:

  • np
    :设备节点
  • propname
    :属性名
  • out_string
    :输出参数,返回字符串指针

返回值是 0 表示成功,负值表示失败(-EINVAL 属性不存在,-ENODATA 属性值为空)。

我们的 LED 驱动用它来读取 status 属性:

constchar* str;
int ret;

ret = of_property_read_string(led.device_tree_node, "status", &str);
if (ret < 0) {
    pr_err("status read failed!\n");
else {
    pr_info("status = %s\n", str);
}

这里需要注意一个细节:如果属性里包含多个字符串(字符串数组),这个函数只会返回第一个。如果你想读取第 N 个字符串,可以用 of_property_read_string_index()

of_property_read_u32:读取 32 位整数

intof_property_read_u32(conststruct device_node *np,
constchar *propname,
                        u32 *out_value)
;

这个函数用于读取单个 32 位整数属性。设备树里的 <0x12345678> 会被解析成一个 u32 值。

参数说明:

  • np
    :设备节点
  • propname
    :属性名
  • out_value
    :输出参数,返回读取的值

返回值是 0 表示成功,负值表示失败。

类似的还有读取 8 位、16 位、64 位整数的版本:

  • of_property_read_u8()
  • of_property_read_u16()
  • of_property_read_u64()

of_property_read_u32_array:读取整数数组

这个函数用于读取包含多个整数的属性,比如 reg 属性:

intof_property_read_u32_array(conststruct device_node *np,
constchar *propname,
                              u32 *out_values,
size_t sz)
;

参数说明:

  • np
    :设备节点
  • propname
    :属性名
  • out_values
    :接收数据的数组指针
  • sz
    :要读取的元素个数

返回值是 0 表示成功,负值表示失败。

我们的 LED 驱动用它来读取 reg 属性:

u32 regdata[10];
int ret;

ret = of_property_read_u32_array(led.device_tree_node, "reg", regdata, 10);
if (ret < 0) {
    pr_err("reg property read failed!\n");
    of_node_put(led.device_tree_node);
return -EINVAL;
}

pr_info("reg data:\n");
for (int i = 0; i < 10; i++) {
    pr_cont("%#X ", regdata[i]);
}
pr_cont("\n");

这里我们预先知道 reg 属性有 10 个整数(5 组地址-长度对),所以直接读 10 个。在实际驱动里,你可能需要先用 of_property_count_elems_of_size() 来获取元素个数,动态分配内存。

of_property_count_elems_of_size:计算数组元素个数

intof_property_count_elems_of_size(conststruct device_node *np,
constchar *propname,
int elem_size)
;

这个函数返回指定属性里有多少个指定大小的元素。比如你想知道 reg 属性里有多少个 u32,可以这样做:

int count = of_property_count_elems_of_size(np, "reg"sizeof(u32));

返回值是元素个数,负值表示出错。


内存映射 API:如何将设备树地址转换为可访问的虚拟地址

我们前面讲了如何从设备树里读取地址值,但那些只是物理地址(或者总线地址)。驱动程序要访问这些地址,还需要把它们映射到内核虚拟地址空间。这一步通常用 ioremap() 来完成。

但 OF API 提供了更便捷的方法,把"读 reg 属性"和"ioremap"两步合成一步。

of_iomap:一步到位的地址映射

这是驱动里最常用的函数之一:

void __iomem *of_iomap(struct device_node *np,
int index)
;

参数说明:

  • np
    :设备节点
  • index
    reg 属性的索引(从 0 开始)

返回值是映射后的内核虚拟地址,失败返回 NULL

这个函数会自动完成以下步骤:

  1. 从 reg 属性里读取第 index 组地址
  2. 处理地址转换(如果需要的话)
  3. 调用 ioremap() 建立映射

我们的 LED 驱动用它来映射所有寄存器地址:

/* 5. 使用 of_iomap 进行寄存器地址映射 */
led.ccm_ccgr1 = of_iomap(led.device_tree_node, 0);
led.sw_mux_gpio = of_iomap(led.device_tree_node, 1);
led.sw_pad_gpio = of_iomap(led.device_tree_node, 2);
led.gpio_dr = of_iomap(led.device_tree_node, 3);
led.gpio_gdir = of_iomap(led.device_tree_node, 4);

if (!led.ccm_ccgr1 || !led.sw_mux_gpio || !led.sw_pad_gpio ||
    !led.gpio_dr || !led.gpio_gdir) {
    pr_err("ioremap failed!\n");
    of_node_put(led.device_tree_node);
return -ENOMEM;
}

这里我们连续调用了 5 次 of_iomap(),每次传入不同的索引。这些索引对应 reg 属性里的 5 组地址:

reg = < 0X020C406C 0X04  /* 索引 0: CCM_CCGR1_BASE */
        0X020E0068 0X04  /* 索引 1: SW_MUX_GPIO1_IO03_BASE */
        0X020E02F4 0X04  /* 索引 2: SW_PAD_GPIO1_IO03_BASE */
        0X0209C000 0X04  /* 索引 3: GPIO1_DR_BASE */
        0X0209C004 0X04 >; /* 索引 4: GPIO1_GDIR_BASE */

注意这里有个重要的错误处理:我们检查了所有映射是否成功,只要有一个失败就报错退出。这点很重要,因为部分成功会导致后续代码访问空指针,引发内核 panic。

of_get_address:获取地址原始数据

有时候你不想直接映射,而是想先拿到地址的原始数据,这时候可以用 of_get_address()

const __be32 *of_get_address(struct device_node *dev,
int index,
                             u64 *size,
unsignedint *flags)
;

参数说明:

  • dev
    :设备节点
  • index
    reg 属性的索引
  • size
    :输出参数,返回地址长度
  • flags
    :输出参数,返回标志(比如 IORESOURCE_MEM

返回值是读取到的地址数据指针(大端格式的 u32 数组),失败返回 NULL

这个函数返回的是设备树里的原始数据,可能还需要地址转换才能变成 CPU 物理地址。

of_translate_address:地址转换

设备树里的地址有时是总线地址,需要转换成 CPU 物理地址:

u64 of_translate_address(struct device_node *dev,
const __be32 *in_addr)
;

参数说明:

  • dev
    :设备节点
  • in_addr
    :从 of_get_address() 拿到的地址

返回值是转换后的物理地址,如果是 OF_BAD_ADDR 表示转换失败。

of_address_to_resource:转换成标准资源结构

Linux 内核用 struct resource 统一描述各种资源。这个函数把设备树里的 reg 直接转成 resource

intof_address_to_resource(struct device_node *dev,
int index,
struct resource *r)
;

参数说明:

  • dev
    :设备节点
  • index
    reg 属性的索引
  • r
    :输出的 resource 结构体

返回值是 0 表示成功,负值表示失败。

这个函数在某些场景下很实用,比如你需要把地址信息传递给其他子系统时。但在简单的字符设备驱动里,直接用 of_iomap() 往往更方便。


资源管理 API:如何正确释放引用

到这里我们讲的都是"获取"资源的 API,但 Linux 内核编程有个黄金法则:有获取就必须有释放。OF API 也不例外。

of_node_put:释放节点引用

当你用 of_find_xxx() 系列函数获取了一个 device_node 指针后,你就有了对这个节点的引用。内核用引用计数来管理这些节点,当你用完后必须调用 of_node_put() 来释放引用:

voidof_node_put(struct device_node *node);

参数 node 是你要释放的节点指针。

我们的 LED 驱动在出错处理和反初始化函数里都用到了它:

/* 出错处理 */
ret = of_property_read_u32_array(led.device_tree_node, "reg", regdata, 10);
if (ret < 0) {
    pr_err("reg property read failed!\n");
    of_node_put(led.device_tree_node);  /* 释放节点引用 */
return -EINVAL;
}

/* 反初始化函数 */
voidled_hw_deinit(void) {
/* ... 先 unmap 所有地址 ... */

if (led.device_tree_node) {
        of_node_put(led.device_tree_node);
        led.device_tree_node = NULL;
    }
}

这里有个小技巧:我们在释放引用后把指针设为 NULL。这样即使 deinit() 函数被多次调用,也不会 double-free。

你可能会问:of_find_property() 需要配合 of_node_put() 吗?答案是:不需要property 结构体是 device_node 的一部分,它的生命周期由节点管理。你只需要在用完整个节点后调用一次 of_node_put() 就行了。


实战示例:LED 驱动中的设备树使用

讲了这么多 API,现在我们把它们串起来,看看在实际驱动里是怎么用的。我们以 LED 硬件控制代码为例,完整走一遍流程。

第一步:查找节点

staticconstchar* kIMX_AES_LED = "/imx_aes_led";

led.device_tree_node = of_find_node_by_path(kIMX_AES_LED);
if (led.device_tree_node == NULL) {
    pr_err("dtsled node can not found!\n");
return -EINVAL;
}
pr_info("dtsled node has been found!\n");

这里我们用路径查找节点。如果没找到,直接返回错误。注意这里还没释放引用,因为后面还要用这个节点。

第二步:读取属性(调试用)

/* 读取 compatible 属性 */
proper = of_find_property(led.device_tree_node, "compatible"NULL);
if (proper == NULL) {
    pr_err("compatible property find failed\n");
else {
    pr_info("compatible = %s\n", (char*)proper->value);
}

/* 读取 status 属性 */
ret = of_property_read_string(led.device_tree_node, "status", &str);
if (ret < 0) {
    pr_err("status read failed!\n");
else {
    pr_info("status = %s\n", str);
}

这两步主要是为了调试,确认我们找到了正确的节点,并且节点状态是 "okay"。在实际生产代码里,这些调试信息可以去掉或改成 pr_debug()

第三步:读取 reg 属性

ret = of_property_read_u32_array(led.device_tree_node, "reg", regdata, 10);
if (ret < 0) {
    pr_err("reg property read failed!\n");
    of_node_put(led.device_tree_node);
return -EINVAL;
}

pr_info("reg data:\n");
for (int i = 0; i < 10; i++) {
    pr_cont("%#X ", regdata[i]);
}
pr_cont("\n");

这里我们读取 reg 属性的所有 10 个整数。注意出错处理里调用了 of_node_put(),避免内存泄漏。

第四步:映射寄存器地址

led.ccm_ccgr1 = of_iomap(led.device_tree_node, 0);
led.sw_mux_gpio = of_iomap(led.device_tree_node, 1);
led.sw_pad_gpio = of_iomap(led.device_tree_node, 2);
led.gpio_dr = of_iomap(led.device_tree_node, 3);
led.gpio_gdir = of_iomap(led.device_tree_node, 4);

if (!led.ccm_ccgr1 || !led.sw_mux_gpio || !led.sw_pad_gpio ||
    !led.gpio_dr || !led.gpio_gdir) {
    pr_err("ioremap failed!\n");
    of_node_put(led.device_tree_node);
return -ENOMEM;
}

这里我们用 of_iomap() 一次性完成地址读取和映射。注意检查了所有映射是否成功,只要有一个失败就全部回滚。

第五步:硬件初始化

/* 使能 GPIO1 时钟 */
val = readl(led.ccm_ccgr1);
pr_info("CCGR1 raw value: 0x%08x\n Bits: ", val);
pr_bin_u32(val);
pr_cont("\n");

val &= ~(3 << 26); /* 清除以前的设置 */
val |= (3 << 26);  /* 设置新值 */
writel(val, led.ccm_ccgr1);

/* 设置 GPIO1_IO03 复用功能为 GPIO */
writel(5, led.sw_mux_gpio);

/* 设置 GPIO1_IO03 电气属性 */
writel(0x10B0, led.sw_pad_gpio);

/* 设置 GPIO1_IO03 为输出功能 */
val = readl(led.gpio_gdir);
val &= ~(3 << 3); /* 清除以前的设置 */
val |= (1 << 3);  /* 设置为输出 */
writel(val, led.gpio_gdir);

/* 默认关闭 LED (高电平) */
val = readl(led.gpio_dr);
val |= (1 << 3);
writel(val, led.gpio_dr);

到这里,我们已经完成了从设备树读取配置到初始化硬件的完整流程。注意这里的寄存器操作(readl()/writel())操作的是映射后的虚拟地址,而不是设备树里的物理地址。

第六步:资源释放

voidled_hw_deinit(void) {
    pr_info("Deinit LED Hardware\n");

if (led.ccm_ccgr1) {
        iounmap(led.ccm_ccgr1);
        led.ccm_ccgr1 = NULL;
    }
/* ... 其他 iounmap ... */

if (led.device_tree_node) {
        of_node_put(led.device_tree_node);
        led.device_tree_node = NULL;
    }
}

卸载驱动时,我们释放所有映射的地址和节点引用。注意这里我们把指针设为 NULL,防止 double-free。


常见错误及处理方法

在实际使用 OF API 时,有几个常见的坑需要特别注意。

错误 1:忘记检查返回值

几乎所有 OF API 都有返回值,你必须检查它们:

/* 错误示例 */
structdevice_node *node = of_find_node_by_path("/some-node");
/* 直接用 node,没检查 NULL */
of_property_read_u32(node, "some-prop", &val);

/* 正确示例 */
structdevice_node *node = of_find_node_by_path("/some-node");
if (!node) {
    pr_err("node not found\n");
return -ENODEV;
}
ret = of_property_read_u32(node, "some-prop", &val);
if (ret) {
    pr_err("property read failed: %d\n", ret);
    of_node_put(node);
return ret;
}

错误 2:忘记释放引用

这是内存泄漏的常见原因:

/* 错误示例 */
structdevice_node *node = of_find_node_by_path("/some-node");
/* 用完后没有调用 of_node_put() */

/* 正确示例 */
structdevice_node *node = of_find_node_by_path("/some-node");
/* ... 使用 node ... */
of_node_put(node);

错误 3:数组长度不匹配

用 of_property_read_u32_array() 时,确保你分配的数组足够大:

/* 危险示例 */
u32 data[5];
of_property_read_u32_array(node, "reg", data, 10);  /* 数组越界! */

/* 安全示例 */
int count = of_property_count_elems_of_size(node, "reg"sizeof(u32));
u32 *data = kmalloc(count * sizeof(u32), GFP_KERNEL);
if (!data) return -ENOMEM;
of_property_read_u32_array(node, "reg", data, count);
/* ... 用完后 ... */
kfree(data);

错误 4:重复映射

不要对同一个地址调用多次 of_iomap()

/* 错误示例 */
void __iomem *addr1 = of_iomap(node, 0);
void __iomem *addr2 = of_iomap(node, 0);  /* 重复映射! */

/* 正确做法 */
void __iomem *addr = of_iomap(node, 0);
/* 后续直接用 addr */

进阶:在内核源码中验证 API

上面我们介绍了十几个 OF API 函数。你可能已经跟着代码敲了一遍,也可能只是大概浏览了一下。不管怎样,当你准备自己写驱动的时候,一个问题迟早会冒出来:这些 API 真的存在吗?我的内核版本里能不能用?

这个问题不是在开玩笑。嵌入式开发有个特点:你手边的内核版本可能比最新的主线内核落后好几年,芯片厂商又会在自己的内核里加一些私货。你在网上看到的教程代码、在某个开源项目里看到的 API 调用,到你自己的内核里可能就编译不过了——要么函数签名不一样,要么头文件路径不对,最惨的是这个 API 根本就不存在。

所以,作为一名负责任的驱动开发者,我们需要养成一个习惯:在正式使用某个 API 之前,先在内核源码里验证它的存在性和正确性。这听起来很繁琐,但比起在生产环境踩坑,这点时间成本绝对是值得的。

验证方法论:grep 的艺术

验证 API 要回答三个问题:

  1. 这个 API 函数定义在哪个头文件里?
  2. 它的函数签名是什么?参数类型、返回值分别是什么?
  3. 在不同内核版本间,这个 API 有没有变化?

内核源码动辄几十万文件,用 grep -r 递归搜索效率很低。更好的方式是:

  1. 只搜索 include/ 目录——API 的声明都在头文件里
  2. 使用 git grep 而不是普通的 grep,前者更快
  3. 加上 -n 参数显示行号,方便定位

比如验证 of_find_node_by_path

cd /home/charliechen/imx-forge/third_party/linux_mainline
git grep -n "of_find_node_by_path" include/linux/of.h

输出:

include/linux/of.h:282:static inline struct device_node *of_find_node_by_path(const char *path)
include/linux/of.h:526:static inline struct device_node *of_find_node_by_path(const char *path)

同一个函数声明出现两次,是因为内核头文件的保护机制:第一处是真正的函数声明,第二处是当 CONFIG_OF 未定义时的空实现。所以搜索时需要看上下文,不能只看函数名。

主线内核验证:核心 API 一览

我们的验证环境是主线内核(linux_mainline),这是 Linux 内核的"官方版本",API 定义是最正统的参考。以下是我们上面用到过的核心 API 的验证结果:

of_find_node_by_path(定义在 include/linux/of.h):

externstruct device_node *of_find_node_opts_by_path(constchar *path,
constchar **opts)
;
staticinlinestruct device_node *of_find_node_by_path(constchar *path)
{
return of_find_node_opts_by_path(path, NULL);
}

有意思的发现:of_find_node_by_path 实际上是个 inline 函数,内部调用了 of_find_node_opts_by_path。参数是设备树节点的路径字符串,返回找到的节点指针(未找到则 NULL)。

of_property_read_string(定义在 include/linux/of.h):

externintof_property_read_string(conststruct device_node *np,
constchar *propname,
constchar **out_string)
;

注意 out_string 是指向指针的指针(const char **),函数不会复制字符串内容,而是直接指向设备树里存储的原始数据。你不需要手动释放这个字符串。

of_property_read_u32_array(定义在 include/linux/of.h):

staticinlineintof_property_read_u32_array(conststruct device_node *np,
constchar *propname,
                         u32 *out_values, size_t sz)

参数 sz 是你想读取多少个元素(不是字节数!)。返回值 0 成功,-EINVAL 属性不存在,-ENODATA 属性没有值,-EOVERFLOW 属性数据比你想要的要小。

of_iomap(定义在 include/linux/of_address.h):

externvoid __iomem *of_iomap(struct device_node *device, int index);

注意这个头文件路径:它不在 of.h 里,而是在单独的 of_address.h 里。使用时需要额外包含:

#include<linux/of.h>
#include<linux/of_address.h>/* 专门为地址映射 API */

of_node_put(定义在 include/linux/of.h):

#ifdef CONFIG_OF_DYNAMIC
externvoidof_node_put(struct device_node *node);
#else
staticinlinevoidof_node_put(struct device_node *node) { }
#endif

有个有趣的发现:of_node_put 的实现取决于 CONFIG_OF_DYNAMIC。如果这个选项没开启,它就是个空函数。但为了代码的可移植性,我们还是应该始终调用它。

IMX 内核对比:确认兼容性

主线内核是参考标准,但我们实际用的是 NXP 的 IMX 内核。芯片厂商在移植时可能做修改。我们在 IMX 内核里搜索同样的 API:

cd /home/charliechen/imx-forge/third_party/linux-imx
git grep -B3 -A3 "of_find_node_by_path" include/linux/of.h

对比结果:上面验证的所有核心 API(of_find_node_by_pathof_property_read_stringof_property_read_u32_arrayof_iomapof_node_put),在主线内核和 IMX 内核中函数签名完全一致。我们可以放心使用这些 API,不用担心兼容性问题。

但这并不意味着所有 API 都是这样。使用高级功能(中断、时钟、GPIO 子系统)时,还是需要仔细验证。

API 差异的常见类型

虽然我们验证的这些核心 API 在两个内核间是一致的,但现实中确实存在差异。常见情况包括:

函数签名变化——最常见。旧版本可能只有 3 个参数,新版本加了第 4 个。应对策略:写代码前先确认你的内核版本对应的头文件。

头文件路径变化——有些 API 在不同版本里被移到了不同头文件。比如 of_gpio.h 在较新的内核里被重构了,一些函数移到了 linux/gpio/consumer.h。应对策略:编译不过时看报错信息,用 grep 搜索它现在在哪个头文件里。

行为变化——最隐蔽。函数签名没变,但行为变了。应对策略:用 git log -p --all -S "function_name" 查看函数的历史修改。

自动化验证脚本

每次手动 grep 确实繁琐。这里提供一个批量验证脚本思路:

#!/bin/bash
# batch_verify.sh - 批量验证多个 OF API

KERNEL_DIR=$1
shift
APIS=("$@")

echo"Verifying ${#APIS[@]} APIs in $KERNEL_DIR..."
echo"=============================================="

for api in"${APIS[@]}"do
echo""
echo"Checking: $api"
    result=$(git -C "$KERNEL_DIR" grep -l "$api" include/ --include="*.h" 2>/dev/null)
if [ -n "$result" ]; then
echo"  Found in: $result"
else
echo"  NOT FOUND"
fi
done

使用方式:

./batch_verify.sh /home/charliechen/imx-forge/third_party/linux_mainline \
    of_find_node_by_path \
    of_property_read_string \
    of_property_read_u32_array \
    of_iomap \
    of_node_put

建立验证习惯

我的建议是:每次你看到一个不熟悉的 API,先花一分钟验证一下。一分钟的时间可以避免后续几小时的调试痛苦。验证时记住这几个关键点:

  1. 确认头文件位置——知道该 include 哪个文件
  2. 确认函数签名——知道参数类型和返回值
  3. 确认是否有版本差异——如果你在多个内核版本间移植代码
  4. 看看函数注释——内核开发者写的注释通常很有参考价值

还有一个建议:把你验证过的 API 记录下来。可以是一个简单的 markdown 文件,也可以是你自己的笔记工具。当你下次再用到这个 API 时,就不用重复验证了。


相关阅读

  1. 通用GUI编程技术——Win32 原生编程实战(五十三)——子类化与超类化 - 相似度 67%
  2. 现代Qt开发教程(新手篇)1.15——正则与文本处理 - 相似度 58%

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 23:13:21 HTTP/2.0 GET : https://f.mffb.com.cn/a/493965.html
  2. 运行时间 : 0.109283s [ 吞吐率:9.15req/s ] 内存消耗:4,620.59kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=d2addf9ef995a41b33a95d8f892e83a6
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000612s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000807s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000351s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000305s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000513s ]
  6. SELECT * FROM `set` [ RunTime:0.000207s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000611s ]
  8. SELECT * FROM `article` WHERE `id` = 493965 LIMIT 1 [ RunTime:0.001776s ]
  9. UPDATE `article` SET `lasttime` = 1783091602 WHERE `id` = 493965 [ RunTime:0.012977s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.002735s ]
  11. SELECT * FROM `article` WHERE `id` < 493965 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000580s ]
  12. SELECT * FROM `article` WHERE `id` > 493965 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000839s ]
  13. SELECT * FROM `article` WHERE `id` < 493965 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001053s ]
  14. SELECT * FROM `article` WHERE `id` < 493965 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.005804s ]
  15. SELECT * FROM `article` WHERE `id` < 493965 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.010219s ]
0.110923s