当前位置:首页>Linux>Linux(31)-pinctrl系统

Linux(31)-pinctrl系统

  • 2026-08-22 03:23:00
Linux(31)-pinctrl系统

Linux(31)-pinctrl系统

前言

一、为什么需要 Pinctrl 子系统?

引脚复用冲突

芯片的引脚资源有限,一个引脚往往对应多种功能。如果多个驱动直接操作寄存器配置功能,很容易出现"多个驱动抢占同一引脚"的冲突,且没有任何机制来检测和阻止这种冲突。

例如,一个引脚可能同时支持以下功能:

  • 普通 GPIO(通用输入输出)
  • I2C 的 SDA/SCL
  • SPI 的 MOSI/MISO
  • UART 的 TX/RX
  • PWM 信号输出 具体复用哪个功能,通过配置芯片内部的复用寄存器(MUX Register) 来选择。

假设芯片上 GPIO2_PB4 这个引脚可以同时复用为普通 GPIO 或 I2C1 的 SDA 信号。在没有 Pinctrl 子系统的时代,两个驱动各自直接操作寄存器:

/* ========== 驱动A:某个LED控制驱动 ========== */
staticintmy_led_probe(struct platform_device *pdev)
{
/* 硬编码:直接把 GPIO2_PB4 配置为 GPIO 输出模式 */
/* 假设 IOMUX 寄存器基地址为 0x020E0000 */
/* GPIO2_PB4 对应的 MUX 寄存器偏移为 0x00DC */
void __iomem *mux_reg = ioremap(0x020E0000 + 0x00DC4);

/* 写入 0x5 —— ALT5 模式,即 GPIO 功能 */
    writel(0x5, mux_reg);

/* 然后把这个引脚当 GPIO 用,控制 LED 闪烁 */
/* ... */
return0;
}
/* ========== 驱动B:I2C1 总线驱动 ========== */
staticintmy_i2c_probe(struct platform_device *pdev)
{
/* 硬编码:把同一个 GPIO2_PB4 配置为 I2C1_SDA 功能 */
void __iomem *mux_reg = ioremap(0x020E0000 + 0x00DC4);

/* 写入 0x0 —— ALT0 模式,即 I2C1_SDA 功能 */
    writel(0x0, mux_reg);

/* 然后初始化 I2C 控制器,开始通信 */
/* ... */
return0;
}

问题所在:两个驱动都直接操作了同一个寄存器地址,后加载的驱动会静默覆盖先加载驱动的配置,且没有任何报错或警告。如果 I2C 驱动后加载,LED 驱动就会莫名其妙地失效——因为它的引脚已经被悄悄切换成了 I2C 功能。没有任何机制能检测到这种冲突。

配置繁琐且易出错

引脚配置不仅要选择"功能",还要设置电气属性:上拉/下拉电阻、驱动能力(drive strength)、电平标准(LVCMOS3.3V/1.8V)、转换速率(slew rate)等。这些都需要操作复杂的寄存器,不同芯片的寄存器地址还不一样,移植性极差。

驱动与硬件严重耦合

驱动代码中充斥着寄存器地址和位操作,换一颗芯片就要重写大量代码,违反了 Linux 内核"硬件与软件分离"的设计哲学。

例如:配置一个引脚的完整电气属性,需要同时操作多个寄存器、计算多个位域:

/*
 * 目标:将 GPIO2_PB4 配置为 I2C1_SDA,要求:
 *   - 复用功能:ALT0(I2C1_SDA)
 *   - 开漏输出
 *   - 47KΩ 上拉电阻
 *   - 驱动能力:4mA
 *   - 转换速率:100MHz
 *   - 电平标准:3.3V LVCMOS
 */

staticvoidconfigure_i2c_sda_pin(void)
{
void __iomem *base = ioremap(0x020E00000x1000);

/* ---- 第一步:配置 MUX 寄存器(选择功能) ---- */
/*
     * 寄存器偏移 0x00DC,位域定义:
     *   [31:20] 保留
     *   [19:16] SION(软件输入使能)
     *   [15:4]  保留
     *   [3:0]   MUX_MODE(复用模式选择)
     *
     * ALT0 = 0x0 → I2C1_SDA
     */

    writel(0x00000000, base + 0x00DC);

/* ---- 第二步:配置 PAD 寄存器(电气属性) ---- */
/*
     * 寄存器偏移 0x0328,位域定义:
     *   [31:18] 保留
     *   [17]    HYS(施密特触发器):1=使能
     *   [16]    PUS[1](上拉/下拉选择)
     *   [15:14] PUS[0] + PUE(上拉/下拉使能)
     *   [13:12] PKE(保持器使能)
     *   [11:7]  ODE(开漏)+ DSE(驱动强度)
     *   [6:3]   SPEED(转换速率)
     *   [2:0]   保留
     *
     * 计算目标值:
     *   HYS=1     → bit17 = 1
     *   47K上拉   → PUS=10, PUE=1 → bits[16:14] = 110
     *   PKE=1     → bits[13:12] = 01
     *   ODE=1     → bit11 = 1(开漏)
     *   DSE=010   → bits[5:3] = 010(4mA)
     *   SPEED=10  → bits[6:3] = 10(100MHz)
     *
     * 拼出来:0x4001B8B0
     *
     * 请问:你能一眼看出 0x4001B8B0 每个位代表什么吗?
     */

    writel(0x4001B8B0, base + 0x0328);

/* ---- 第三步:如果该引脚是输入,还要配置输入选择寄存器 ---- */
/*
     * 寄存器偏移 0x0618
     * 用于选择信号从哪个物理引脚进入内部逻辑
     */

    writel(0x1, base + 0x0618);

    iounmap(base);
}
  • 魔数满天飞:0x020E0000、0x00DC、0x0328、0x4001B8B0 全是硬编码的魔数,没有任何自描述性。半年后你自己都看不懂 0x4001B8B0 到底配了什么。
  • 位域计算极易出错:一个上拉电阻的配置需要同时操作 PUS、PUE、PKE 三个位域共 5 个 bit,手动拼值非常容易算错。
  • 移植性为零:换一个芯片(比如从 i.MX6ULL 换到 RK3576),寄存器基地址、偏移地址、位域定义全部不同,这段代码需要从头重写。
  • 无冲突检测:如果另一个驱动也把 0x0328 这个地址配成了不同的值,后写的会覆盖先写的,系统不会有任何提示。

对比:有了 Pinctrl 之后同样的配置,在设备树中只需要这样写:

pinctrl_i2c1: i2c1grp {
    fsl,pins = <
        MX6UL_PAD_I2C1_SDA__I2C1_SDA  0x4001b8b0
    >;
};
  • MX6UL_PAD_I2C1_SDA__I2C1_SDA 自带寄存器地址和复用模式,不需要手动计算
  • 如果另一个驱动也引用了同一个引脚,Pinctrl 核心层会直接返回 -EBUSY 错误,拒绝冲突请求
  • 换芯片时只需要修改设备树,驱动代码一行都不用动

概念分析

pinctrl 子系统作用

1. 引脚复用 (Pin Multiplexing / Pinmux)

  • 单个物理 Pad(焊盘)内部连接着一个多路复用开关(MUX)。pinctrl 记录了芯片上每一个引脚可被复用的所有功能(Function),并在外设申请时完成内部路由配置。

2. 电气属性配置 (Pin Configuration / Pinconf)

  • 除了控制逻辑切换,引脚的物理电气特性同样关键:
  • Pull-up / Pull-down:上拉/下拉电阻配置,决定引脚悬空时的默认电平。
  • Drive Strength:驱动能力(通常以 mA 为单位,如 2mA, 4mA, 8mA, 12mA),影响信号边沿陡峭度与抗干扰能力。
  • Open-Drain / Push-Pull:开漏与推挽模式设置。
  • Slew Rate / Schmitt Trigger:转换速率控制与施密特触发器(用于信号去噪与整形)。

3. 动态状态管理 (State Management) pinctrl 支持将一组引脚打包定义为不同的“状态”(State):

  • default:设备正常工作时的引脚配置。
  • sleep:设备进入休眠时的配置(如切为高阻态或下拉,防止静态漏电)。
  • idle:总线空闲时的配置(如 I2C/SPI 在不传输数据时的低功耗配置)。

此外还有两个重要概念:

  • Pin Group(引脚组):一组具有相似功能的引脚集合。例如 UART0 需要 TX 和 RX 两个引脚,它们可以归为一个 group。
  • Function(功能):一个逻辑功能(如 "uart0"、"i2c0"),关联一个或多个 pin group。

例如:

信号
物理引脚
复用功能名
TX
GPIO1_IO18 (Pin 18)
UART1_TX_DATA
RX
GPIO1_IO19 (Pin 19)
UART1_RX_DATA
RTS
GPIO1_IO20 (Pin 20)
UART1_RTS_B
CTS
GPIO1_IO21 (Pin 21)
UART1_CTS_B

但实际使用中,有些设计只用 TX/RX(两线制),有些需要硬件流控(四线制)。这就产生了两种不同的引脚组合需求.

在芯片厂商的 pinctrl 驱动代码中(如 pinctrl-imx6ul.c),这些数据被定义为静态表:

/* ===== 第一层:Pin 定义 ===== */
/* 每个物理引脚的编号和名称 */
staticconststructpinctrl_pin_descimx6ul_pins[] = {
    ...
    IMX_PIN(18"GPIO1_IO18"),   /* 编号 18 */
    IMX_PIN(19"GPIO1_IO19"),   /* 编号 19 */
    IMX_PIN(20"GPIO1_IO20"),   /* 编号 20 */
    IMX_PIN(21"GPIO1_IO21"),   /* 编号 21 */
    ...
};

/* ===== 第二层:Pin Group 定义 ===== */
/* 引脚组1:基本通信(TX + RX) */
staticconstunsignedint uart1_tx_rx_pins[] = { 1819 };

/* 引脚组2:硬件流控(RTS + CTS) */
staticconstunsignedint uart1_rts_cts_pins[] = { 2021 };

/* 引脚组数组:UART1 有两个 group */
staticconstchar * const uart1_groups[] = {
"uart1_tx_rx",       /* group 0:基本通信 */
"uart1_rts_cts",     /* group 1:硬件流控 */
};

/* ===== 第三层:Function 定义 ===== */
/* "uart1" 这个功能,关联上面两个 group */
staticconststructimx_pmx_funcimx6ul_pmx_funcs[] = {
    ...
    {
        .name       = "uart1",           /* 功能名 */
        .groups     = uart1_groups,      /* 关联的 group 数组 */
        .num_groups = ARRAY_SIZE(uart1_groups),  /* 共 2 个 group */
    },
    ...
};

关系映射图

Function: "uart1"
├── Group: "uart1_tx_rx"      ← 基本通信场景
│   ├── Pin 18 (GPIO1_IO18)   → TX
│   └── Pin 19 (GPIO1_IO19)   → RX

└── Group: "uart1_rts_cts"    ← 硬件流控场景(可选)
    ├── Pin 20 (GPIO1_IO20)   → RTS
    └── Pin 21 (GPIO1_IO21)   → CTS

设备树中如何使用

/* 场景A:只用 TX/RX,不需要硬件流控 */
pinctrl_uart1_basic: uart1-basic {
    fsl,pins = <
        MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX  0x1b0b1
        MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX  0x1b0b1
    >;
};

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1_basic>;   /* 只引用基本 group */
    status = "okay";
};

/* 场景B:需要硬件流控,两个 group 都要 */
pinctrl_uart1_full: uart1-full {
    fsl,pins = <
        MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX  0x1b0b1
        MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX  0x1b0b1
        MX6UL_PAD_UART1_CTS_B__UART1_DCE_CTS   0x1b0b1
        MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS   0x1b0b1
    >;
};

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1_full>;    /* 引用包含全部引脚的 group */
    status = "okay";
};

pinctrl 实现架构

Pinctrl 子系统采用经典的三层架构设计:

其底层驱动的核心数据结构以(pinctrl-rk3568.c)为例:

底层驱动关键函数

描述引脚控制器:pinctrl_desc + pinctrl_dev

// 描述一个引脚控制器的"能力说明书"
structpinctrl_desc {
constchar *name;
conststructpinctrl_pin_desc *pins;// 所有引脚的描述数组
unsignedint npins;                     // 引脚总数
conststructpinctrl_ops *pctlops;// 引脚组操作回调
conststructpinmux_ops *pmxops;// 复用操作回调
conststructpinconf_ops *confops;// 电气配置操作回调
};

// 内核运行时创建的控制器实例
structpinctrl_dev {
structlist_headnode;// 全局链表节点
structpinctrl_desc *desc;// 指向描述符
structradix_tree_rootpin_desc_tree;// 引脚描述符的基数树
structlist_headgpio_ranges;// GPIO 范围映射
structdevice *dev;
void *driver_data;
};

描述单个引脚:pinctrl_pin_desc

structpinctrl_pin_desc {
unsigned number;      // 引脚编号,全局唯一
constchar *name;     // 引脚名称(如 "GPIO2_PB4")
void *drv_data;       // 驱动私有数据
};

描述三种操作回调:pinctrl_ops(引脚组操作):

structpinctrl_ops {
int (*get_groups_count)(struct pinctrl_dev *pctldev);
constchar *(*get_group_name)(struct pinctrl_dev *pctldev, unsigned selector);
int (*get_group_pins)(struct pinctrl_dev *pctldev, unsigned selector, ...);
int (*dt_node_to_map)(...);   // 解析设备树节点为 pin map
void (*dt_free_map)(...);     // 释放 pin map
};

pinmux_ops(复用操作):

structpinmux_ops {
int (*request)(struct pinctrl_dev *pctldev, unsigned offset);   // 请求引脚
int (*free)(struct pinctrl_dev *pctldev, unsigned offset);      // 释放引脚
int (*get_functions_count)(struct pinctrl_dev *pctldev);
constchar *(*get_function_name)(struct pinctrl_dev *pctldev, unsigned selector);
int (*get_function_groups)(struct pinctrl_dev *pctldev, unsigned selector, ...);
int (*set_mux)(struct pinctrl_dev *pctldev, unsigned func_selector, unsigned group_selector);
bool strict;  // 是否禁止 GPIO 和其他功能同时使用同一引脚
};

pinconf_ops(电气配置操作):

structpinconf_ops {
int (*pin_config_get)(struct pinctrl_dev *pctldev, unsigned pin, unsignedlong *config);
int (*pin_config_set)(struct pinctrl_dev *pctldev, unsigned pin, unsignedlong *configs, unsigned num_configs);
int (*pin_config_group_get)(...);
int (*pin_config_group_set)(...);
};

描述外设的引脚状态:pinctrl + pinctrl_state + pinctrl_setting

// 每个使用 pinctrl 的外设设备都有一个 pinctrl 结构体
structpinctrl {
structlist_headnode;
structdevice *dev;
structlist_headstates;// 状态链表(default、sleep 等)
structpinctrl_state *state;// 当前激活的状态
};

// 一个状态(如 "default")
structpinctrl_state {
structlist_headnode;
constchar *name;                 // 状态名:"default"、"sleep"、"idle"
structlist_headsettings;// 该状态下的所有设置项
};

// 单个设置项(复用或配置)
structpinctrl_setting {
structlist_headnode;
enum pinctrl_map_type type;       // MUX_GROUP 或 CONFIGS_PIN/GROUP
structpinctrl_dev *pctldev;// 由哪个控制器处理
union {
structpinctrl_setting_muxmux;// 复用设置
structpinctrl_setting_configsconfigs;// 电气配置
    } data;
};

Pin Map:连接设备树与硬件的桥梁

Pin map 是 pinctrl 的核心数据结构,它将设备树中的配置信息转化为内核可操作的结构:

structpinctrl_map {
constchar *dev_name;             // 使用该引脚的设备名
constchar *name;                 // 状态名(如 "default")
enum pinctrl_map_type type;       // 类型:MUX_GROUP / CONFIGS_PIN / CONFIGS_GROUP
constchar *ctrl_dev_name;        // 引脚控制器设备名
union {
structpinctrl_map_muxmux;// { group, function }
structpinctrl_map_configsconfigs;// { group_or_pin, configs[], num_configs }
    } data;
};

实现流程

先分清三大阵营

阵营A:控制器侧(芯片厂商定义,描述"我有哪些引脚、能做什么"
  pinctrl_desc / pinctrl_dev / pinctrl_pin_desc / 三种 ops

阵营B:消费者侧(每个外设设备各一套,描述"我需要引脚怎么配"
  pinctrl / pinctrl_state / pinctrl_setting

阵营C:桥梁(临时产物,把设备树翻译成阵营B能用的格式)
  pinctrl_map

讲pinctrl 底层实现流程前需县了解两个probe

Linux 内核中,任何驱动的 probe 函数被调用,前提都是驱动(driver)和设备(device)匹配成功。匹配方式有多种,在嵌入式设备树系统中,最常见的就是 compatible 属性匹配:

在 pinctrl 的语境下,系统中实际存在两次独立的匹配和 probe 过程:

第一个 probe:pinctrl 控制器自身的 probe

设备树(通常在芯片级 dtsi 中):
┌─────────────────────────────────────────┐
│  iomuxc: pinctrl@20e0000 {             │
│      compatible = "fsl,imx6ul-iomuxc"; │
│      reg = <0x020e00000x4000>;        │
│      ...                                │
│  };                                     │
└─────────────────────────────────────────┘
              │
              │  compatible 匹配
              ▼
┌─────────────────────────────────────────┐
│  pinctrl-imx6ul.c 中的 probe:          │
│                                         │
│  staticintimx6ul_pinctrl_probe(...)   │
│  
{                                      │
│      // 1. ioremap 寄存器基地址          │
│      // 2. 填充 pinctrl_desc            │
│      // 3. 填充引脚表、功能表            │
│      // 4. 调用 devm_pinctrl_register() │
│      //     → 阵营A就位                 │
│  }                                      │
└─────────────────────────────────────────┘

这个 probe 的职责是把阵营A注册进内核,让 pinctrl 子系统知道"这颗芯片有哪些引脚、支持哪些复用功能"。它在系统启动早期就完成了。

第二个 probe:使用 pinctrl 的外设驱动的 probe

设备树(板级 dts 中):
┌─────────────────────────────────────────┐
│  &i2c1 {                                │
│      compatible = "fsl,imx6ul-i2c";    │
│      pinctrl-names = "default";         │
│      pinctrl-0 = <&pinctrl_i2c1>;      │
│  };                                     │
└─────────────────────────────────────────┘
              │
              │  compatible 匹配
              ▼
┌─────────────────────────────────────────┐
│  really_probe() 被调用                  │
│                                         │
│  ① pinctrl_bind_pins()  ← 自动执行     │
│     解析 pinctrl-0,配置引脚             │
│     阵营C → 阵营B → 写寄存器            │
│                                         │
│  ② i2c_imx_probe()  ← 驱动的 probe     │
│     此时引脚已配好,直接使用              │
└─────────────────────────────────────────┘

这个 probe 的职责是初始化外设本身(如初始化 I2C 控制器、注册 adapter 等)。而在它被调用之前,内核已经自动通过 pinctrl_bind_pins() 完成了引脚配置。

时间线

系统启动
  │
  ├── ① pinctrl 控制器 probe(系统早期)
  │     匹配 "fsl,imx6ul-iomuxc"
  │     → 注册 pinctrl_desc / pinctrl_dev
  │     → 阵营A就位
  │
  ├── ② I2C 驱动 probe(稍后)
  │     匹配 "fsl,imx6ul-i2c"
  │     → 内核先自动调用 pinctrl_bind_pins()
  │       → 解析设备树 pinctrl-0
  │       → 生成 pinctrl_map(阵营C)
  │       → 转换为 pinctrl_state/setting(阵营B)
  │       → 调用 set_mux + pin_config_set 写寄存器
  │     → 然后才调用 I2C 驱动的 probe()
  │
  └── ③ 其他外设驱动 probe(更后)
        ... 同样的流程 ...

了解完上面的两个probe后开始分析pinctrl底层实现流程第一步。

第一步:控制器注册(系统启动早期)

芯片厂商的 pinctrl 驱动 probe 时,把阵营A的数据注册进内核

芯片厂商在代码中静态定义好:
┌─────────────────────────────────────────────────────────┐
│  pinctrl_desc(能力说明书)                                │
│  ├── name = "pinctrl-imx6ul"                            │
│  ├── pins → pinctrl_pin_desc 数组                        │
│  │     [0]  { number=0,  name="GPIO1_IO00" }            │
│  │     [1]  { number=1,  name="GPIO1_IO01" }            │
│  │     ...                                               │
│  │     [41] { number=41, name="GPIO2_PB3" } ← I2C1_SCL  │
│  │     [42] { number=42, name="GPIO2_PB4" } ← I2C1_SDA  │
│  │     ...                                               │
│  ├── npins = 142                                         │
│  ├── pctlops → pinctrl_ops(引脚组查询回调)              │
│  ├── pmxops  → pinmux_ops(复用操作回调)                 │
│  └── confops → pinconf_ops(电气配置回调)                │
└─────────────────────────────────────────────────────────┘
              │
              │  调用 devm_pinctrl_register(&desc)
              ▼
┌─────────────────────────────────────────────────────────┐
│  pinctrl_dev(运行时实例,核心层自动创建)                  │
│  ├── desc → 指向上面的 pinctrl_desc                      │
│  ├── pin_desc_tree → 把 142 个 pinctrl_pin_desc 建成索引 │
│  ├── gpio_ranges → 空(后续 GPIO 子系统来填充)           │
│  └── node → 挂入全局 pinctrl_list 链表                   │
└─────────────────────────────────────────────────────────┘
  • 此时阵营A就位了,但还没有任何外设来"使用"这些引脚。pinctrl_dev 是核心层根据 pinctrl_desc 创建的运行时对象,desc 是"图纸",dev 是"施工后的实体"。

第二步:设备树解析(外设 probe 前)

当 I2C1 设备匹配到驱动后,内核在调用 probe 之前,先解析设备树中的 pinctrl-0:

设备树中的原始数据:
  &i2c1 {
      pinctrl-names = "default";
      pinctrl-0 = <&pinctrl_i2c1>;
  };

  pinctrl_i2c1: i2c1grp {
      fsl,pins = <
0x00900x031C0x05880x20x00x4001b8b0
0x00940x03200x058C0x20x00x4001b8b0
      >;
  };
              │
              │  核心层调用阵营A中 pinctrl_ops 的 dt_node_to_map() 回调
              │  (这是芯片厂商实现的,不同芯片解析方式不同)
              ▼
┌─────────────────────────────────────────────────────────┐
│  pinctrl_map 数组(阵营C,翻译产物)                       │
│                                                         │
│  map[0]:                                                │
│    dev_name      = "21a0000.i2c"     ← 谁要用            │
│    name          = "default"         ← 什么状态下用       │
│    type          = MUX_GROUP         ← 这是复用配置       │
│    ctrl_dev_name = "pinctrl-imx6ul"  ← 找哪个控制器      │
│    data.mux = { group="i2c1", function="i2c1" }         │
│                                                         │
│  map[1]:                                                │
│    dev_name      = "21a0000.i2c"                         │
│    name          = "default"                             │
│    type          = CONFIGS_GROUP     ← 这是电气配置       │
│    ctrl_dev_name = "pinctrl-imx6ul"                      │
│    data.configs = { group="i2c1", configs={0x4001b8b0} }│
└─────────────────────────────────────────────────────────┘
  • pinctrl_map 是临时翻译产物,它把设备树中芯片厂商自定义的 fsl,pins 格式翻译成核心层能统一处理的标准格式。翻译完成后,它的使命基本结束。

第三步:转换为运行时状态结构

核心层将 pinctrl_map 进一步转化为阵营B的链表结构:

pinctrl_map(临时,翻译完即弃)
              │
              │  核心层内部转换
              ▼
┌──────────────────────────────────────────────────────────────────┐
│  pinctrl(阵营B,I2C1 设备专属)                                  │
│  ├── dev → 指向 I2C1 设备                                        │
│  ├── state → 当前激活的状态(初始为 NULL,select 后指向 default) │
│  └── states 链表:                                               │
│      │                                                           │
│      └── pinctrl_state "default"                                 │
│          ├── name = "default"                                    │
│          └── settings 链表:                                     │
│              │                                                   │
│              ├── pinctrl_setting [0]                             │
│              │   type = MUX_GROUP                                │
│              │   pctldev → 指向阵营A的 pinctrl_dev  ◄──────── 关键连接!
│              │   data.mux = { func=3, group=7 }                  │
│              │                                                   │
│              └── pinctrl_setting [1]                             │
│                  type = CONFIGS_GROUP                            │
│                  pctldev → 指向阵营A的 pinctrl_dev  ◄──────── 关键连接!
│                  data.configs = { group=7, configs={0x4001b8b0} }│
└──────────────────────────────────────────────────────────────────┘

  • pinctrl_setting 中的 pctldev 指针是整个系统的核心连接点——它把消费者侧的配置需求(阵营B)和控制器侧的执行能力(阵营A)桥接在一起。

第四步:执行配置(写寄存器)

pinctrl_select_state(p, "default")
    │
    │  遍历 default 状态下的 settings 链表
    ▼
setting[0]: type = MUX_GROUP
    │
    │  通过 pctldev 找到控制器
    │  通过 pctldev->desc->pmxops 找到回调函数
    ▼
pctldev->desc->pmxops->set_mux(pctldev, func=3, group=7)
    │
    │  控制器驱动内部:
    │  根据 group=7 查表找到该 group 包含的引脚 [4142]
    │  对每个引脚写 MUX 寄存器
    ▼
writel(mux_value, base + 0x0090)   ← Pin 41 (SCL) 的 MUX 寄存器
writel(mux_value, base + 0x0094)   ← Pin 42 (SDA) 的 MUX 寄存器

setting[1]: type = CONFIGS_GROUP
    │
    ▼
pctldev->desc->confops->pin_config_set(pctldev, pin, configs)
    │
    ▼
writel(0x4001b8b0, base + 0x031C)  ← Pin 41 的 PAD 寄存器
writel(0x4001b8b0, base + 0x0320)  ← Pin 42 的 PAD 寄存器

整体流程

┌─────────────────── 阵营A:控制器侧 ───────────────────┐
│                                                       │
│  pinctrl_desc ──────▶ pinctrl_dev                     │
│  (静态图纸)           (运行时实例)                      │
│       │                    │                           │
│       ├── pins[]           ├── desc → 回指 desc        │
│       │   pinctrl_pin_desc ├── pin_desc_tree           │
│       │                    └── node → 全局链表          │
│       │                                               │
│       ├── pctlops → pinctrl_ops                       │
│       │     └── dt_node_to_map()  ← 翻译设备树        │
│       │                                               │
│       ├── pmxops → pinmux_ops                         │
│       │     └── set_mux()  ← 写复用寄存器              │
│       │                                               │
│       └── confops → pinconf_ops                       │
│             └── pin_config_set()  ← 写电气寄存器       │
│                                                       │
└───────────────────────────┬───────────────────────────┘
                            │
                  pctldev 指针桥接
                            │
┌───────────────────────────▼───────────────────────────┐
│                                                       │
│  ┌─────────── 阵营C:桥梁 ──────────┐                 │
│  │  pinctrl_map(临时翻译产物)      │                 │
│  │  设备树原始数据 → 标准格式        │                 │
│  └──────────────┬───────────────────┘                 │
│                 │ 转换                                 │
│                 ▼                                      │
│  ┌─────────── 阵营B:消费者侧 ──────┐                 │
│  │  pinctrl                         │                 │
│  │  └── pinctrl_state "default"     │                 │
│  │      └── pinctrl_setting         │                 │
│  │          ├── pctldev ────────────────── 指向阵营A   │
│  │          ├── type = MUX/CONFIGS  │                 │
│  │          └── data = {func, group}│                 │
│  └──────────────────────────────────┘                 │
│                                                       │
└───────────────────────────────────────────────────────┘

阵营A(desc/dev/ops)是芯片厂商告诉内核"我有什么能力";阵营C(map)是设备树配置被翻译成的标准中间格式;阵营B(pinctrl/state/setting)是每个外设设备最终持有的、可直接执行的配置队列。数据从 A → C → B → A 单向流动,pinctrl_setting 中的 pctldev 指针是连接消费者需求和控制器能力的唯一桥梁。

使用pinctrl 例子

下面基于 i.MX6ULL 平台,给出两个完整的 pinctrl 使用示例。

示例一:LED 灯点亮

1.设备树

/ {
/*
     * LED 设备节点
     * 使用内核自带的 gpio-leds 驱动,无需自己写驱动
     * compatible 匹配 drivers/leds/leds-gpio.c
     */

    leds {
        compatible = "gpio-leds";

/* 板子上的用户 LED,连接到 GPIO1_IO18 */
        user_led {
            label = "board:user_led";          /* sysfs 中显示的名字 */
            gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>; /* GPIO1 的第 18 号引脚,高电平点亮 */
default-state = "off";             /* 默认关闭 */
            linux,default-trigger = "none";    /* 不使用自动触发 */

/*
             * 引用 pinctrl 配置节点
             * 内核会在 probe 前自动将 GPIO1_IO18 配置为 GPIO 模式
             */

            pinctrl-names = "default";
            pinctrl-0 = <&pinctrl_user_led>;
        };
    };
};

/*
 * IOMUXC 控制器节点中添加 pinctrl 配置
 * &iomuxc 是芯片级 dtsi 中已定义的 pinctrl 控制器
 */

&iomuxc {
/*
     * pinctrl_user_led:LED 引脚的复用和电气配置
     *
     * MX6UL_PAD_UART1_TX_DATA__GPIO1_IO18 是宏,展开后包含:
     *   mux_reg 偏移 = 0x0074
     *   conf_reg 偏移 = 0x0300
     *   input_reg 偏移 = 0x0000(GPIO 输出不需要输入选择)
     *   mux_mode = 5(ALT5 = GPIO 功能)
     *   input_val = 0
     *
     * 0x17059 是 PAD 电气属性配置值:
     *   HYS=1(施密特触发器,抗干扰)
     *   PUS=11(22K 上拉)
     *   PUE=1(使用上下拉模式)
     *   PKE=1(使能)
     *   SPEED=10(100MHz 中速)
     *   DSE=100(R0/4 中等驱动能力)
     *   SRE=1(快压摆)
     */

    pinctrl_user_led: user_led_grp {
        fsl,pins = <
            MX6UL_PAD_UART1_TX_DATA__GPIO1_IO18  0x17059
        >;
    };
};

2.驱动代码

/*
 * 本例使用内核自带的 gpio-leds 驱动(drivers/leds/leds-gpio.c),
 * 无需编写自定义驱动。内核通过 compatible = "gpio-leds" 自动匹配。
 *
 * 下面展示如果不用 gpio-leds 驱动,自己写一个最简 LED 驱动是什么样的。
 * 实际项目中推荐直接使用 gpio-leds,这里仅作教学演示。
 */


#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/gpio/consumer.h>    /* GPIO descriptor API */
#include<linux/of.h>
#include<linux/sysfs.h>
#include<linux/device.h>

structmy_led_data {
structgpio_desc *led_gpio;/* GPIO 描述符,由 gpiod_get 获取 */
int state;                      /* 当前 LED 状态:0=灭,1=亮 */
};

/*
 * 通过 sysfs 属性节点控制 LED
 * 用户空间执行:echo 1 > /sys/devices/.../state
 */

staticssize_tstate_show(struct device *dev,
                          struct device_attribute *attr, char *buf)

{
structmy_led_data *data = dev_get_drvdata(dev);
returnsprintf(buf, "%d\n", data->state);
}

staticssize_tstate_store(struct device *dev,
                           struct device_attribute *attr,
constchar *buf, size_t count)

{
structmy_led_data *data = dev_get_drvdata(dev);
int val;

if (kstrtoint(buf, 10, &val) < 0)
return -EINVAL;

    data->state = val ? 1 : 0;

/*
     * gpiod_set_value:设置 GPIO 电平
     * 内部会检查 pinctrl 是否已将该引脚配置为 GPIO 功能
     * 如果 pinctrl 未正确配置,这里可能无法正常工作
     */

    gpiod_set_value(data->led_gpio, data->state);

return count;
}

staticDEVICE_ATTR_RW(state);

staticintmy_led_probe(struct platform_device *pdev)
{
structmy_led_data *data;
int ret;

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;

/*
     * 获取 GPIO 描述符
     * "led" 对应设备树中 gpios = <...> 的属性名
     * GPIOD_OUT_LOW 表示初始化为输出、低电平
     *
     * 注意:在调用 probe 之前,内核已经通过 pinctrl_bind_pins()
     * 将 GPIO1_IO18 的 MUX 寄存器设置为 ALT5(GPIO 模式),
     * 所以这里可以直接请求 GPIO 使用
     */

    data->led_gpio = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(data->led_gpio)) {
        dev_err(&pdev->dev, "Failed to get GPIO\n");
return PTR_ERR(data->led_gpio);
    }

/* 创建 sysfs 属性节点 */
    ret = device_create_file(&pdev->dev, &dev_attr_state);
if (ret) {
        dev_err(&pdev->dev, "Failed to create sysfs entry\n");
return ret;
    }

    platform_set_drvdata(pdev, data);
    dev_info(&pdev->dev, "My LED driver probed\n");

return0;
}

staticintmy_led_remove(struct platform_device *pdev)
{
    device_remove_file(&pdev->dev, &dev_attr_state);
return0;
}

/* 匹配表:compatible = "my,custom-led" */
staticconststructof_device_idmy_led_of_match[] = {
    { .compatible = "my,custom-led", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_led_of_match);

staticstructplatform_drivermy_led_driver = {
    .probe  = my_led_probe,
    .remove = my_led_remove,
    .driver = {
        .name = "my-custom-led",
        .of_match_table = my_led_of_match,
    },
};
module_platform_driver(my_led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Demo");
MODULE_DESCRIPTION("Custom LED driver using pinctrl + GPIO");

应用层代码

/*
 * led_ctrl.c
 * 用户空间控制 LED 的应用程序
 *
 * 使用内核自带 gpio-leds 驱动时,通过 sysfs 控制:
 *   echo 1 > /sys/class/leds/board:user_led/brightness   点亮
 *   echo 0 > /sys/class/leds/board:user_led/brightness   熄灭
 *
 * 使用自定义驱动时,通过自定义 sysfs 节点控制:
 *   echo 1 > /sys/devices/.../state   点亮
 *   echo 0 > /sys/devices/.../state   熄灭
 *
 * 本程序演示通过 sysfs 文件操作来控制 LED
 */


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

/* 内核自带 gpio-leds 驱动的 sysfs 路径 */
#define LED_BRIGHTNESS_PATH  "/sys/class/leds/board:user_led/brightness"

/*
 * 向 sysfs 文件写入一个字符('0' 或 '1')
 * 返回值:0 成功,-1 失败
 */

staticintwrite_sysfs(constchar *path, constchar *value)
{
int fd;

    fd = open(path, O_WRONLY);
if (fd < 0) {
        perror("open sysfs failed");
return-1;
    }

if (write(fd, value, strlen(value)) < 0) {
        perror("write sysfs failed");
        close(fd);
return-1;
    }

    close(fd);
return0;
}

intmain(int argc, char *argv[])
{
if (argc < 2) {
printf("用法: %s <on|off|blink>\n", argv[0]);
printf("  on    - 点亮 LED\n");
printf("  off   - 熄灭 LED\n");
printf("  blink - 让 LED 以 heartbeat 模式闪烁\n");
return-1;
    }

if (strcmp(argv[1], "on") == 0) {
/* 写入 "1" 点亮 LED */
        write_sysfs(LED_BRIGHTNESS_PATH, "1");
printf("LED 已点亮\n");

    } elseif (strcmp(argv[1], "off") == 0) {
/* 写入 "0" 熄灭 LED */
        write_sysfs(LED_BRIGHTNESS_PATH, "0");
printf("LED 已熄灭\n");

    } elseif (strcmp(argv[1], "blink") == 0) {
/*
         * 切换到 heartbeat 触发模式
         * gpio-leds 驱动会自动让 LED 按心跳节奏闪烁
         */

        write_sysfs("/sys/class/leds/board:user_led/trigger""heartbeat");
printf("LED 已进入 heartbeat 闪烁模式\n");
    } else {
printf("未知命令: %s\n", argv[1]);
return-1;
    }

return0;
}

示例二:I2c 读取温度

设备树

/*
 * I2C1 控制器节点
 * pinctrl 配置不变,引脚复用和电气属性由 pinctrl 子系统自动处理
 */

&i2c1 {
#address-cells = <1>;
#size-cells = <0>;
    clock-frequency = <100000>;
    status = "okay";

    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;

    lm75@48 {
        compatible = "my,lm75";
        reg = <0x48>;
    };
};

&iomuxc {
    pinctrl_i2c1: i2c1_grp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO00__I2C1_SCL  0x4001b8b0
            MX6UL_PAD_GPIO1_IO01__I2C1_SDA  0x4001b8b0
        >;
    };
};

驱动代码:自定义字符设备方式

/*
 * my_lm75_chrdev.c
 *
 * LM75 温度传感器驱动 —— 自定义字符设备版本
 *
 * 驱动注册一个字符设备 /dev/i2c_test,应用层通过 ioctl 读取温度。
 * 这样应用层不需要知道 I2C 地址、寄存器地址等底层细节,
 * 只需要 open("/dev/i2c_test") 然后 ioctl 即可。
 */


#include<linux/module.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/uaccess.h>
#include<linux/of.h>
#include<linux/slab.h>

/* ========== 自定义 ioctl 命令定义 ========== */

/*
 * ioctl 命令定义规范:
 * _IOR(类型, 编号, 数据类型)  —— 从驱动读数据到用户空间
 * _IOW(类型, 编号, 数据类型)  —— 从用户空间写数据到驱动
 * _IOWR(类型, 编号, 数据类型) —— 双向读写
 *
 * 宏参数说明:
 *   类型:用一个字符标识设备类别,'T' 表示 Temperature
 *   编号:命令编号,从 0 开始递增
 *   数据类型:传输的数据结构
 */

#define LM75_IOCTL_MAGIC    'T'

/* 读取温度值,返回毫摄氏度整数 */
#define LM75_IOC_GET_TEMP       _IOR(LM75_IOCTL_MAGIC, 0, int)

/* 读取温度值,返回带小数点的字符串,如 "25.5" */
#define LM75_IOC_GET_TEMP_STR   _IOR(LM75_IOCTL_MAGIC, 1, char[32])

/* 读取 LM75 配置寄存器的原始值(调试用) */
#define LM75_IOC_GET_CONFIG     _IOR(LM75_IOCTL_MAGIC, 2, int)

/* 设置过温报警阈值,单位毫摄氏度 */
#define LM75_IOC_SET_OS_THRESH  _IOW(LM75_IOCTL_MAGIC, 3, int)

/* ========== LM75 寄存器定义 ========== */
#define LM75_REG_TEMP    0x00    /* 温度寄存器(只读,2字节) */
#define LM75_REG_CONF    0x01    /* 配置寄存器(读写,1字节) */
#define LM75_REG_THYST   0x02    /* 迟滞温度阈值 */
#define LM75_REG_TOS     0x03    /* 过温报警阈值 */

/* ========== 设备私有数据 ========== */

structmy_lm75_data {
structi2c_client *client;/* I2C 客户端(用于 I2C 通信) */
structcdevcdev;/* 字符设备结构体 */
dev_t devno;                  /* 设备号 */
structclass *cls;/* 设备类(用于自动创建设备节点) */
structdevice *device;/* 设备结构体(对应 /dev/i2c_test) */
};

/* 全局设备号(静态分配,实际项目中建议动态分配) */
staticdev_t lm75_devno;
/* 全局设备数据指针(单设备场景,多实例时应该用链表管理) */
staticstructmy_lm75_data *g_lm75_data;

/* ========== 字符设备文件操作 ========== */

/*
 * open 操作
 * 应用层调用 open("/dev/i2c_test", O_RDWR) 时触发
 * 将设备私有数据挂到 file->private_data,供后续 read/write/ioctl 使用
 */

staticintlm75_open(struct inode *inode, struct file *filp)
{
/*
     * container_of:通过 cdev 成员反推出包含它的 my_lm75_data 结构体
     * inode->i_cdev 指向注册时绑定的 cdev
     */

structmy_lm75_data *data = container_of(inode->i_cdev,
structmy_lm75_datacdev);

    filp->private_data = data;
return0;
}

/*
 * release 操作
 * 应用层调用 close() 时触发
 */

staticintlm75_release(struct inode *inode, struct file *filp)
{
return0;
}

/*
 * ioctl 操作 —— 核心接口
 *
 * 应用层通过 ioctl(fd, cmd, arg) 调用到这里
 * cmd 决定执行什么操作,arg 是用户空间传入的参数指针
 *
 * 这是产品级驱动最常用的用户空间接口方式:
 *   - 接口清晰,应用层只需知道命令号
 *   - 可以传输任意复杂的数据结构
 *   - 不暴露底层 I2C 细节
 */

staticlonglm75_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg)
{
structmy_lm75_data *data = filp->private_data;
structi2c_client *client = data->client;
int ret;

switch (cmd) {

case LM75_IOC_GET_TEMP: {
/*
         * 读取温度,返回毫摄氏度整数
         * 例如:25500 表示 25.5°C
         */

int raw;
int temp_x2;
int mcelsius;

/* 从 LM75 读取 16 位原始温度值 */
        raw = i2c_smbus_read_word_swapped(client, LM75_REG_TEMP);
if (raw < 0)
return raw;

/*
         * LM75 数据格式:
         * 16 位原始值中,高 9 位有效(1 位符号 + 8 位整数),
         * 第 7 位表示 0.5°C。
         * 右移 7 位后得到以 0.5°C 为单位的值。
         */

        temp_x2 = raw >> 7;
if (temp_x2 & 0x01)
            mcelsius = (temp_x2 / 2) * 1000 + 500;
else
            mcelsius = (temp_x2 / 2) * 1000;

/* 将结果拷贝到用户空间 */
if (copy_to_user((int __user *)arg, &mcelsius, sizeof(int)))
return -EFAULT;
break;
    }

case LM75_IOC_GET_TEMP_STR: {
/*
         * 读取温度,返回可读字符串,如 "25.5"
         * 方便应用层直接显示,不需要自己做数值转换
         */

int raw;
int temp_x2;
int integer_part;
int frac_part;
char buf[32];

        raw = i2c_smbus_read_word_swapped(client, LM75_REG_TEMP);
if (raw < 0)
return raw;

        temp_x2 = raw >> 7;
        integer_part = temp_x2 / 2;
        frac_part = (temp_x2 & 0x01) ? 5 : 0;

snprintf(buf, sizeof(buf), "%d.%d", integer_part, frac_part);

/* 将字符串拷贝到用户空间 */
if (copy_to_user((char __user *)arg, buf, strlen(buf) + 1))
return -EFAULT;
break;
    }

case LM75_IOC_GET_CONFIG: {
/*
         * 读取配置寄存器原始值(调试用途)
         */

int conf;

        conf = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
if (conf < 0)
return conf;

if (copy_to_user((int __user *)arg, &conf, sizeof(int)))
return -EFAULT;
break;
    }

case LM75_IOC_SET_OS_THRESH: {
/*
         * 设置过温报警阈值
         * 用户传入毫摄氏度值,驱动转换为 LM75 寄存器格式后写入
         */

int mcelsius;
int raw_val;

/* 从用户空间拷贝参数 */
if (copy_from_user(&mcelsius, (int __user *)arg, sizeof(int)))
return -EFAULT;

/*
         * 将毫摄氏度转换为 LM75 寄存器格式
         * LM75 阈值寄存器格式与温度寄存器相同:高 9 位有效
         * 整数部分左移 7 位,0.5°C 部分设置 bit7
         */

        raw_val = (mcelsius / 1000) << 7;
if ((mcelsius % 1000) >= 500)
            raw_val |= (1 << 7);

        ret = i2c_smbus_write_word_swapped(client, LM75_REG_TOS, raw_val);
if (ret < 0)
return ret;
break;
    }

default:
return -ENOTTY;    /* 不支持的命令 */
    }

return0;
}

/*
 * read 操作(可选)
 * 有些驱动也会实现 read,让应用层用 read() 直接读温度
 * 这里也提供,作为 ioctl 的补充
 */

staticssize_tlm75_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)

{
structmy_lm75_data *data = filp->private_data;
structi2c_client *client = data->client;
int raw, temp_x2, mcelsius;
char kbuf[32];
int len;

/* 读取温度 */
    raw = i2c_smbus_read_word_swapped(client, LM75_REG_TEMP);
if (raw < 0)
return raw;

    temp_x2 = raw >> 7;
if (temp_x2 & 0x01)
        mcelsius = (temp_x2 / 2) * 1000 + 500;
else
        mcelsius = (temp_x2 / 2) * 1000;

/* 格式化为字符串 */
    len = snprintf(kbuf, sizeof(kbuf), "%d\n", mcelsius);

/* 拷贝到用户空间 */
if (copy_to_user(buf, kbuf, len))
return -EFAULT;

return len;
}

/* 字符设备文件操作集合 */
staticconststructfile_operationslm75_fops = {
    .owner          = THIS_MODULE,
    .open           = lm75_open,
    .release        = lm75_release,
    .unlocked_ioctl = lm75_ioctl,
    .read           = lm75_read,
};

/* ========== I2C 驱动 probe/remove ========== */

/*
 * I2C 驱动 probe
 *
 * 执行顺序:
 *   1. pinctrl 子系统已自动配置好 SCL/SDA 引脚为 I2C 功能
 *   2. I2C 控制器已初始化
 *   3. 这里注册字符设备,创建 /dev/i2c_test 节点
 */

staticintmy_lm75_probe(struct i2c_client *client,
const struct i2c_device_id *id)

{
structmy_lm75_data *data;
int ret;

/* 检查 I2C 适配器能力 */
if (!i2c_check_functionality(client->adapter,
                                 I2C_FUNC_SMBUS_WORD_DATA |
                                 I2C_FUNC_SMBUS_BYTE_DATA)) {
        dev_err(&client->dev, "Adapter doesn't support required SMBus ops\n");
return -EIO;
    }

/* 分配私有数据 */
    data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;

    data->client = client;
    i2c_set_clientdata(client, data);

/* 验证 I2C 通信:读取配置寄存器 */
    ret = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
if (ret < 0) {
        dev_err(&client->dev, "Failed to communicate with LM75: %d\n", ret);
        dev_err(&client->dev, "Check pinctrl and wiring!\n");
goto err_free;
    }
    dev_info(&client->dev, "LM75 detected, config reg: 0x%02x\n", ret);

/*
     * ===== 注册字符设备 =====
     */


/* 步骤1:分配设备号 */
    ret = alloc_chrdev_region(&lm75_devno, 01"i2c_test");
if (ret < 0) {
        dev_err(&client->dev, "Failed to alloc chrdev: %d\n", ret);
goto err_free;
    }
    data->devno = lm75_devno;

/* 步骤2:初始化 cdev 并绑定文件操作 */
    cdev_init(&data->cdev, &lm75_fops);
    data->cdev.owner = THIS_MODULE;

/* 步骤3:将 cdev 添加到内核 */
    ret = cdev_add(&data->cdev, lm75_devno, 1);
if (ret < 0) {
        dev_err(&client->dev, "Failed to add cdev: %d\n", ret);
goto err_unreg;
    }

/*
     * 步骤4:创建设备类
     * 设备类用于 udev/mdev 自动创建设备节点
     * 没有这一步,/dev/i2c_test 节点不会自动出现
     */

    data->cls = class_create(THIS_MODULE, "i2c_test_class");
if (IS_ERR(data->cls)) {
        ret = PTR_ERR(data->cls);
goto err_cdev;
    }

/*
     * 步骤5:创建设备节点
     * 这会在 /dev/ 下创建 "i2c_test" 节点
     * udev/mdev 会根据 class 信息自动执行 mknod
     */

    data->device = device_create(data->cls, NULL, lm75_devno,
NULL"i2c_test");
if (IS_ERR(data->device)) {
        ret = PTR_ERR(data->device);
goto err_class;
    }

    g_lm75_data = data;

    dev_info(&client->dev, "LM75 char device /dev/i2c_test registered\n");
    dev_info(&client->dev, "  Major: %d, Minor: %d\n",
             MAJOR(lm75_devno), MINOR(lm75_devno));
return0;

/* 错误处理的回退路径(逆序释放) */
err_class:
    class_destroy(data->cls);
err_cdev:
    cdev_del(&data->cdev);
err_unreg:
    unregister_chrdev_region(lm75_devno, 1);
err_free:
    kfree(data);
return ret;
}

/*
 * I2C 驱动 remove
 * 模块卸载或设备移除时调用,逆序释放所有资源
 */

staticintmy_lm75_remove(struct i2c_client *client)
{
structmy_lm75_data *data = i2c_get_clientdata(client);

    device_destroy(data->cls, data->devno);
    class_destroy(data->cls);
    cdev_del(&data->cdev);
    unregister_chrdev_region(data->devno, 1);
    kfree(data);

    g_lm75_data = NULL;
return0;
}

/* 设备树匹配表 */
staticconststructof_device_idmy_lm75_of_match[] = {
    { .compatible = "my,lm75", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_lm75_of_match);

/* I2C 设备 ID 表 */
staticconststructi2c_device_idmy_lm75_id[] = {
    { "my_lm75"0 },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, my_lm75_id);

/* I2C 驱动结构体 */
staticstructi2c_drivermy_lm75_driver = {
    .driver = {
        .name = "my-lm75-chrdev",
        .of_match_table = my_lm75_of_match,
    },
    .probe    = my_lm75_probe,
    .remove   = my_lm75_remove,
    .id_table = my_lm75_id,
};
module_i2c_driver(my_lm75_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Demo");
MODULE_DESCRIPTION("LM75 temperature sensor driver with char device interface");

应用层代码

/*
 * temp_read_chrdev.c
 *
 * 通过 /dev/i2c_test 字符设备读取 LM75 温度
 *
 * 编译:gcc -o temp_read_chrdev temp_read_chrdev.c
 * 运行:./temp_read_chrdev
 */


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>

/* ========== ioctl 命令定义(必须与驱动中一致) ========== */
#define LM75_IOCTL_MAGIC    'T'
#define LM75_IOC_GET_TEMP       _IOR(LM75_IOCTL_MAGIC, 0, int)
#define LM75_IOC_GET_TEMP_STR   _IOR(LM75_IOCTL_MAGIC, 1, char[32])
#define LM75_IOC_GET_CONFIG     _IOR(LM75_IOCTL_MAGIC, 2, int)
#define LM75_IOC_SET_OS_THRESH  _IOW(LM75_IOCTL_MAGIC, 3, int)

/* 设备节点路径 */
#define DEV_PATH    "/dev/i2c_test"

intmain(int argc, char *argv[])
{
int fd;
int ret;

printf("=== LM75 温度采集(字符设备方式)===\n\n");

/* 打开字符设备 */
    fd = open(DEV_PATH, O_RDWR);
if (fd < 0) {
        perror("open /dev/i2c_test failed");
printf("请确认:\n");
printf("  1. 驱动已加载:lsmod | grep lm75\n");
printf("  2. 设备节点存在:ls -l /dev/i2c_test\n");
printf("  3. 权限足够:sudo ./temp_read_chrdev\n");
return-1;
    }

/* ===== 方式1:ioctl 读取温度整数(毫摄氏度) ===== */
    {
int temp_mc;

        ret = ioctl(fd, LM75_IOC_GET_TEMP, &temp_mc);
if (ret < 0) {
            perror("ioctl GET_TEMP failed");
        } else {
printf("[ioctl 整数] 温度: %d 毫摄氏度 = %.1f °C\n",
                   temp_mc, temp_mc / 1000.0);
        }
    }

/* ===== 方式2:ioctl 读取温度字符串 ===== */
    {
char temp_str[32] = {0};

        ret = ioctl(fd, LM75_IOC_GET_TEMP_STR, temp_str);
if (ret < 0) {
            perror("ioctl GET_TEMP_STR failed");
        } else {
printf("[ioctl 字符串] 温度: %s °C\n", temp_str);
        }
    }

/* ===== 方式3:read 读取温度 ===== */
    {
char buf[64] = {0};

        ret = read(fd, buf, sizeof(buf) - 1);
if (ret < 0) {
            perror("read failed");
        } else {
            buf[ret] = '\0';
printf("[read] 温度: %s 毫摄氏度\n", buf);
        }
    }

/* ===== 方式4:ioctl 读取配置寄存器(调试) ===== */
    {
int config;

        ret = ioctl(fd, LM75_IOC_GET_CONFIG, &config);
if (ret < 0) {
            perror("ioctl GET_CONFIG failed");
        } else {
printf("[ioctl 调试] 配置寄存器: 0x%02x\n", config);
        }
    }

/* ===== 方式5:ioctl 设置过温报警阈值为 35.5°C ===== */
    {
int thresh_mc = 35500;    /* 35.5°C = 35500 毫摄氏度 */

        ret = ioctl(fd, LM75_IOC_SET_OS_THRESH, &thresh_mc);
if (ret < 0) {
            perror("ioctl SET_OS_THRESH failed");
        } else {
printf("[ioctl 设置] 过温报警阈值已设为 35.5 °C\n");
        }
    }

/* 关闭设备 */
    close(fd);
printf("\n测试完成\n");

return0;
}

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-22 08:09:54 HTTP/2.0 GET : https://f.mffb.com.cn/a/511763.html
  2. 运行时间 : 0.338945s [ 吞吐率:2.95req/s ] 内存消耗:4,720.86kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=ad0bd7228444c9fbec79a501c557f21c
  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.001006s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001446s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.010330s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.001257s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001450s ]
  6. SELECT * FROM `set` [ RunTime:0.000617s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001699s ]
  8. SELECT * FROM `article` WHERE `id` = 511763 LIMIT 1 [ RunTime:0.013277s ]
  9. UPDATE `article` SET `lasttime` = 1787357395 WHERE `id` = 511763 [ RunTime:0.015833s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000687s ]
  11. SELECT * FROM `article` WHERE `id` < 511763 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.005462s ]
  12. SELECT * FROM `article` WHERE `id` > 511763 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.021420s ]
  13. SELECT * FROM `article` WHERE `id` < 511763 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001892s ]
  14. SELECT * FROM `article` WHERE `id` < 511763 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.030092s ]
  15. SELECT * FROM `article` WHERE `id` < 511763 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.064100s ]
0.342608s