4.3.5. Pinctrl 调试指南
4.3.5.1. 概述
Pinctrl (Pin Control) 是 Linux 内核中用于管理和配置硬件引脚 (如 GPIO 引脚、复用引脚等) 的一个子系统。它的主要功能是为系统提供对硬件引脚的灵活控制,涵盖引脚的功能配置、方向设置、上拉/下拉电阻等选项。Pinctrl功能通常通过配置设备树 (DTS) 文件来实现,从而有效地管理和优化引脚的使用。
4.3.5.2. 特点
芯片中 Pinctrl 的特点如下:
多功能引脚控制:支持 GPIO、UART、SPI、I2C、PWM 等多种功能。
引脚方向与电平配置:可以设置引脚的输入/输出方向及输出电平(高/低)。
引脚复用:支持引脚功能的复用(MUX_ALT),使得一个引脚可以承载多种功能。
电气特性配置:设置上拉/下拉电阻、开漏输出等电气特性。
设备树支持:通过设备树(DTS)配置引脚的功能和电气特性。
灵活的引脚分配:支持根据需求动态分配引脚功能和接口。
可扩展性:支持根据定制化硬件需求进行扩展配置。
4.3.5.3. 功能描述
Pinctrl 典型应用

例如,在 Linux 系统中,若我们希望使用某个引脚来控制 LED 灯的开关,就可以通过 Pinctrl 子系统配置该引脚的功能。通常,这种配置会涉及到引脚的方向、电平以及可能的复用功能。
Pinctrl 功能原理
Pinctrl 的核心原理是通过设备树(DTS)来描述硬件平台的引脚配置。设备树中定义了每个引脚的功能、复用模式、电气特性等。主控设备通过解析设备树中的引脚控制器节点,来初始化和管理引脚的配置。

主控芯片中的关键调用步骤 :
1. 解析设备树中的引脚配置:
设备树中的 pinctrl 配置通常定义了引脚的功能、复用模式、驱动配置等。主控通过解析这些设备树属性来获取硬件引脚的配置信息。
2. 主控初始化和配置引脚控制器
主控在驱动中通过
horizon_pinctrl_probe函数初始化硬件引脚控制器。这一步包括分配内存、设置设备树的引脚控制信息以及解析GPIO bank(引脚组)配置。horizon_pinctrl_parse_gpio_bank:解析设备树中与 GPIO 引脚组相关的信息,为硬件控制器初始化引脚组配置。horizon_pinctrl_probe_dt:进一步解析设备树中的 pinctrl 配置,读取引脚复用 (muxing)、电气配置 (如上拉、下拉)等设置。
3. 设备初始化与注册
使用
devm_pinctrl_register_and_init注册引脚控制器。该函数初始化设备,创建必要的设备结构,并使主控能够使用该引脚控制器
4. 引脚状态管理
引脚的配置和状态通过
pinctrl_commit_state函数应用。设备树中的状态(如默认状态、睡眠状态等)通过 pinctrl_select_state 函数进行设置。这使得主控能够控制引脚的不同工作模式。
5. 引脚请求与配置
主控通过
pin_request函数请求特定引脚。每个引脚在使用前必须被“请求”并分配给某个功能(如 GPIO 或其他设备功能)。请求时会检查引脚是否已被占用,确保没有冲突。
6. 设备树中的引脚复用
pinctrl 通过
pinmux_enable_setting来设置引脚的复用模式。这些设置根据设备树中定义的复用配置(horizon,pins)来选择适当的功能。
Pinctrl 工作方式
Pinctrl 和 GPIO 子系统
在 Linux 中,GPIO 操作是通过 Pinctrl 子系统完成的。驱动层在使用 Pinctrl 时,会进入 Pinctrl 核心层来分析引脚的枚举、映射和配置,从而完成引脚的配置和管理。Pinctrl 子系统有效避免了传统直接操作硬件寄存器时可能出现的引脚冲突和配置错误。

传统的引脚配置方式通常是直接操作相应的寄存器,但这种方式不仅繁琐,而且容易出错(例如引脚功能冲突)为了简化这一过程并避免潜在问题,引入了pinctrl子系统,对于使用者而言,只需要重点关注的工作内容如下:
获取设备树中的引脚信息。
根据获取到的引脚信息配置引脚的复用功能。
根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
Pinctrl 子系统结构

如上图所示,Pinctrl 核心层是 Linux 内核中的抽象层,它为下层的 SoC 引脚控制器驱动(Pin Controller Driver)提供通信接口,并为上层驱动提供引脚控制能力。具体功能包括引脚复用配置、电气特性设置(如上下拉电阻、驱动强度等),并为 GPIO 子系统提供引脚操作接口。
驱动代码
Kernel Space
kernel/drivers/pinctrl/hobot/aon.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/common.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/display.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/dsp.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/hsio.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/lsio.c # Pinctrl 驱动代码源文件
kernel/drivers/pinctrl/hobot/common.h # Pinctrl 驱动代码头文件
内核配置
CONFIG_PINCTRL_SINGLE

Pinctrl的DTS配置
Pinctrl 控制器的设备树定义位于 BSP 源码包的 kernel 文件夹下的arch/arm64/boot/dts/hobot/x5.dtsi文件内。
4.3.5.4. Pinctrl使用
驱动DTS配置
在使用 Pinctrl 前,驱动需要在设备树中配置相关的 Pinctrl 配置组。设备树通过 pinctrl-names 和 pinctrl-0 属性指定引脚控制配置组。驱动程序在 probe 阶段会根据设备树配置,将相应的引脚组配置到寄存器中。
示例: 设备树文件中的 UART2 配置:
/* arch/arm64/boot/dts/hobot/x5-evb.dts */
&uart2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2>;
};
引脚控制配置(pinctrl_uart2):
/* arch/arm64/boot/dts/hobot/pinmux-func.dtsi */
pinctrl_uart2: uart2grp {
horizon,pins = <
LSIO_UART2_RX LSIO_PINMUX_3 BIT_OFFSET16 MUX_ALT0 &pconf_drv_pu_mid_3v3
LSIO_UART2_TX LSIO_PINMUX_3 BIT_OFFSET18 MUX_ALT0 &pconf_drv_pu_mid_3v3
>;
};
};
这段配置说明:
&uart2:这个节点指的是一个名为 uart2 的硬件外设,通常在设备树中,& 符号表示引用一个已经在其他地方定义的外设。这个节点表明你启用了 UART2 外设,并且它的状态是 “okay”,即表示该外设处于启用状态。
status:这行代码表明启用 UART2 外设。
pinctrl-names:定义引脚控制(pin control)名 “default” 代表引脚控制的默认状态。
pinctrl-0: 表示将引脚控制配置为名为 pinctrl_uart2 的配置节点,具体配置信息如下:
pinctrl_uart2: uart2grp:这定义了一个名为 uart2grp 的引脚组,并给它起了一个标签 pinctrl_uart2,用于其他地方引用。
horizon,pins = <…>:该行描述了 UART2 的两个引脚配置,包括接收引脚(LSIO_UART2_RX)和发送引脚(LSIO_UART2_TX)。每个引脚由四个部分组成:
引脚名称(LSIO_UART2_RX 和 LSIO_UART2_TX):这代表 UART2 接收(RX)和发送(TX)引脚,标识了这两个引脚在硬件上的位置。
引脚复用(LSIO_PINMUX_3):代表引脚的复用组。
偏移量(BIT_OFFSET16 和 BIT_OFFSET18):这些是与引脚相关的位偏移,用于定位引脚在寄存器中的位置。
引脚复用功能(MUX_ALT0):MUX_ALT0 表示引脚的复用模式。在这种情况下,表示引脚被配置为 UART 功能(而非其他功能)。
驱动配置(&pconf_drv_pu_mid_3v3):这表示引脚的驱动配置,引用了一个驱动配置节点 pconf_drv_pu_mid_3v3,它定义了引脚的电气属性,如电压域、上下拉、驱动力等。
另外也提供了GPIO功能的配置,在arch/arm64/boot/dts/hobot/pinmux-gpio.dtsi中,内容如下:
/* arch/arm64/boot/dts/hobot/pinmux-gpio.dtsi */
lsio_gpio0_8: lsio_gpio0_8 {
horizon,pins = <
LSIO_UART2_RX LSIO_PINMUX_3 BIT_OFFSET16 MUX_ALT2 &pconf_input_en_3v3
>;
};
lsio_gpio0_9: lsio_gpio0_9 {
horizon,pins = <
LSIO_UART2_TX LSIO_PINMUX_3 BIT_OFFSET18 MUX_ALT2 &pconf_input_en_3v3
>;
};
lsio_gpio0_8: lsio_gpio0_8:这是一个 GPIO 引脚的配置节点,表示 gpio0_8 引脚的设置。该引脚被配置为与 LSIO_UART2_RX(UART2 的接收引脚)相对应的 GPIO 引脚。
horizon,pins = <…>:描述了该引脚的复用设置。这部分的内容与上面的 UART2 配置类似,不同的是 MUX_ALT2 代表该引脚复用功能为 GPIO。
&pconf_input_en_3v3:引用一个名为 pconf_input_en_3v3 的引脚驱动配置,表示该引脚配置为输入模式并且使用 3.3V 电压。
设备树中完整的 pinctrl 属性包括:
Pin-mux配置 :用于引脚配置某个功能,最多支持4种功能。
MUX_ALT0 0x0
MUX_ALT1 0x1
MUX_ALT2 0x2
MUX_ALT3 0x3
drive-strength :引脚电流的驱动级,详细驱动力对应表如下:
IOL Low Level Output Current @VOL (max) = 0.125*VDDIO
| DS (Drive Strength) | Typical Current | Unit |
|---|---|---|
| 0b0000 | 3.8 | mA |
| 0b0001 | 5.7 | mA |
| 0b0010 | 7.5 | mA |
| 0b0011 | 9.4 | mA |
| 0b0100 | 11.3 | mA |
| 0b0101 | 13.2 | mA |
| 0b0110 | 15.0 | mA |
| 0b0111 | 16.9 | mA |
| 0b1000 | 18.8 | mA |
| 0b1001 | 20.7 | mA |
| 0b1010 | 22.6 | mA |
| 0b1011 | 24.4 | mA |
| 0b1100 | 26.3 | mA |
| 0b1101 | 28.2 | mA |
| 0b1110 | 30.0 | mA |
| 0b1111 | 31.9 | mA |
IOH High Level Output Current @VOH (min) = 0.75*VDDIO
| DS (Drive Strength) | Typical Current | Unit |
|---|---|---|
| 0b0000 | 5.3 | mA |
| 0b0001 | 7.9 | mA |
| 0b0010 | 10.6 | mA |
| 0b0011 | 13.2 | mA |
| 0b0100 | 15.8 | mA |
| 0b0101 | 18.5 | mA |
| 0b0110 | 21.1 | mA |
| 0b0111 | 23.7 | mA |
| 0b1000 | 26.3 | mA |
| 0b1001 | 29.0 | mA |
| 0b1010 | 31.6 | mA |
| 0b1011 | 34.2 | mA |
| 0b1100 | 36.9 | mA |
| 0b1101 | 39.5 | mA |
| 0b1110 | 42.1 | mA |
| 0b1111 | 44.7 | mA |
上下拉配置 :每个Pin脚也支持上下拉配置,在arch/arm64/boot/dts/hobot/pinmux-func.dtsi可以设置默认配置,可支持的上下拉配置有:
bias-pull-up
bias-disable
bias-pull-down
施密特触发配置 :施密特触发器能够有效地去除输入信号的噪声并稳定其输出,密特触发器的核心特点是它有一个上下阈值,只有当输入信号穿越这些阈值时,输出才会改变。
每个引脚的 pinctrl 属性配置应根据 《X5 PIN SW Reg.xlsx》 文档去选择,如下图:
第一列:0x38,代表pin reg,例如LSIO的基地址为0x3418_0000,则对应 lsio_uart2_txd_pu 地址 = 基地址 + pin reg 。
第二列:lsio_pin_ctrl_12,代表寄存器地址名。
第三列:RW,RW 通常表示寄存器或内存区域的访问权限。它意味着该区域的内容可以被读取(Read)和写入(Write)
第四列:偏移比特位,代表对应寄存器区域地址。
第五列:对应寄存器的区域。
第六列:代表该寄存器功能的默认值。
第七列:代表不同寄存器的功能描述,例如上下拉,施密特触发,电压域,驱动力。
| 0x38 | lsio_pin_ctrl_12 | RW | [31] | lsio_uart2_ms | 0x0 | SDIO mode select for PAD LSIO_UART2_TXD,LSIO_UART2_RXD, LSIO_UART3_TXD, LSIO_UART3_RXD, LSIO_UART4_TXD, LSIO_UART4_RXD, 0: 3.3v; 1: 1.8v |
|---|---|---|---|---|---|---|
| RSV | [30:15] | RSV | 0x0 | reserved bits | ||
| RW | [14] | lsio_uart2_txd_pu | 0x0 | lsio_uart2_txd pull up enable: 1: pull up enable; 0: pull up disable; | ||
| RW | [13] | lsio_uart2_txd_pd | 0x0 | lsio_uart2_txd pull down enable: 1: pull down enable; 0: pull down disable; | ||
| RW | [12:9] | lsio_uart2_txd_ds | 0x3 | lsio_uart2_txd driving selector; | ||
| RW | [8] | lsio_uart2_txd_st | 0x0 | lsio_uart2_txd; Schmitt trigger enable. 1: enables Schmitt trigger input function; 0: no Schmitt trigger input function; | ||
| RSV | [7] | RSV | 0x0 | reserved bits | ||
| RW | [6] | lsio_uart2_rxd_pu | 0x0 | lsio_uart2_rxd pull up enable: 1: pull up enable; 0: pull up disable; | ||
| RW | [5] | lsio_uart2_rxd_pd | 0x0 | lsio_uart2_rxd pull down enable: 1: pull down enable; 0: pull down disable; | ||
| RW | [4:1] | lsio_uart2_rxd_ds | 0x3 | lsio_uart2_rxd driving selector; | ||
| RW | [0] | lsio_uart2_rxd_st | 0x0 | lsio_uart2_rxd; Schmitt trigger enable. 1: enables Schmitt trigger input function; 0: no Schmitt trigger input function; |
Pinctrl 设备树解析

驱动调用代码
在驱动代码中,首先通过 devm_pinctrl_get 获取引脚控制器。然后,通过 pinctrl_lookup_state 获取并切换到相应的引脚配置状态。
static int hobot_pinctrl_probe(struct platform_device *pdev)
{
...
g_xxx_dev->pinctrl = devm_pinctrl_get(&pdev->dev);
if (IS_ERR(g_xxx_dev->pinctrl)) {
dev_warn(&pdev->dev, "pinctrl get none\n");
g_xxx_dev->pins_xxxx = NULL;
}
...
/* 按照 pinctrl-names lookup state */
g_xxx_dev->pins_xxxx = pinctrl_lookup_state(g_xxx_dev->pinctrl, "xxx_func");
if (IS_ERR(g_xxx_dev->pins_xxxx)) {
dev_info(&pdev->dev, "xxx_func get error %ld\n",
PTR_ERR(g_xxx_dev->pins_xxxx));
g_xxx_dev->pins_xxxx = NULL;
}
...
}
int xxxx_pinmux_select(void)
{
if (!g_xxx_dev->pins_xxxx)
return -ENODEV;
/* 切换到对应的state */
return pinctrl_select_state(g_xxx_dev->pinctrl, g_xxx_dev->pins_xxxx);
}
用户空间调试
Uboot Space
Pinctrl 功能与 I/O 域、GPIO 紧密相关。在 IO-DOMAIN_UBOOT 阶段,可以使用相关命令来调试和修改电压域功能。
Kernel Space
如果在内核配置中打开了 Linux Kernel 的 CONFIG_DEBUG_FS 选项,并且挂载了 debugfs 文件系统,内核已提供了 Pinctrl 的 debugfs 接口。
首先,检查内核是否已经挂载了 debugfs,如果下列命令输出不为空,则代表当前已挂载 debugfs:
mount | grep debugfs
如果输出为空,则执行以下命令挂载 debugfs:
mount -t debugfs none /sys/kernel/debug
确保 debugfs 已挂载后,用户可以在用户空间的 debugfs 下查看 Pinctrl 的信息。
以 lsio 子系统为例,lsio 子系统的 debugfs 节点路径为:/sys/kernel/debug/pinctrl/34180000.lsio_iomuxc/
该目录下的节点就可以查看 lsio 域引脚的 Pinctrl 信息(以下命令输出仅供参考,具体节点以 Linux 内核代码为准):
root@buildroot:~$ ls /sys/kernel/debug/pinctrl/34180000.lsio_iomuxc/
gpio-ranges pinconf-groups pinconf-pins pingroups pinmux-functions pinmux-pins pinmux-select pins
以“pinmux-pins”为例,这个节点可以查看哪些 Pin 脚被配置为配置组。(以下命令输出仅供参考,具体输出会随着dts配置变化)
root@buildroot:~$ cat /sys/kernel/debug/pinctrl/34180000.lsio_iomuxc/pinmux-pins
pin 14 (lsio_spi0_sclk): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 36 (lsio_spi0_ssn): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 37 (lsio_spi0_miso): vdd08_gpu_reg@3 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio1_5
pin 38 (lsio_spi0_mosi): soc:cam:vcon@3 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio1_6
pin 15 (lsio_spi1_ssn_1): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 16 (lsio_spi1_sclk): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 17 (lsio_spi1_ssn): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 18 (lsio_spi1_miso): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 19 (lsio_spi1_mosi): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 20 (lsio_spi2_sclk): 34020000.spi (GPIO UNCLAIMED) function lsio_iomuxc group spi2grp
pin 21 (lsio_spi2_ssn): 34020000.spi (GPIO UNCLAIMED) function lsio_iomuxc group spi2grp
pin 22 (lsio_spi2_miso): 34020000.spi (GPIO UNCLAIMED) function lsio_iomuxc group spi2grp
pin 23 (lsio_spi2_mosi): 34020000.spi (GPIO UNCLAIMED) function lsio_iomuxc group spi2grp
pin 24 (lsio_spi3_sclk): 3d060000.mipi_host (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_24
pin 25 (lsio_spi3_ssn): 34110000.lpwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_lpwm1_1
pin 26 (lsio_spi3_miso): 3d080000.mipi_host (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_26
pin 27 (lsio_spi3_mosi): 3d090000.mipi_host (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_27
pin 28 (lsio_spi4_sclk): 341a0000.serial (GPIO UNCLAIMED) function lsio_iomuxc group uart5grp
pin 29 (lsio_spi4_ssn): 341a0000.serial (GPIO UNCLAIMED) function lsio_iomuxc group uart5grp
pin 30 (lsio_spi4_miso): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 31 (lsio_spi4_mosi): 35010000.gmac-tsn (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_31
pin 32 (lsio_spi5_sclk): 34100000.lpwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_lpwm0_0
pin 33 (lsio_spi5_ssn): 34100000.lpwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_lpwm0_1
pin 34 (lsio_spi5_miso): 34100000.lpwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_lpwm0_2
pin 35 (lsio_spi5_mosi): 34100000.lpwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_lpwm0_3
pin 39 (lsio_i2c0_scl): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 40 (lsio_i2c0_sda): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 41 (lsio_i2c1_scl): 34170000.pwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_pwm3_0
pin 42 (lsio_i2c1_sda): 34170000.pwm (GPIO UNCLAIMED) function lsio_iomuxc group pinctrl_pwm3_1
pin 43 (lsio_i2c2_scl): 340d0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c2grp
pin 44 (lsio_i2c2_sda): 340d0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c2grp
pin 45 (lsio_i2c3_scl): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 46 (lsio_i2c3_sda): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 47 (lsio_i2c4_scl): 340f0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c4grp
pin 48 (lsio_i2c4_sda): 340f0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c4grp
pin 0 (lsio_uart7_rx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_0
pin 1 (lsio_uart7_tx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_1
pin 3 (lsio_uart7_rts): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_3
pin 2 (lsio_uart7_cts): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_2
pin 4 (lsio_uart1_rx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_4
pin 5 (lsio_uart1_tx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_5
pin 7 (lsio_uart1_rts): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_7
pin 6 (lsio_uart1_cts): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_6
pin 8 (lsio_uart2_rx): 34080000.serial (GPIO UNCLAIMED) function lsio_iomuxc group uart2grp
pin 9 (lsio_uart2_tx): 34080000.serial (GPIO UNCLAIMED) function lsio_iomuxc group uart2grp
pin 10 (lsio_uart3_rx): 341c0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c5grp
pin 11 (lsio_uart3_tx): 341c0000.i2c (GPIO UNCLAIMED) function lsio_iomuxc group i2c5grp
pin 12 (lsio_uart4_rx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_12
pin 13 (lsio_uart4_tx): gpiochip8 (GPIO UNCLAIMED) function lsio_iomuxc group lsio_gpio0_13
打印的详细信息举例如下:
pin 36:表示引脚编号是 36。
lsio_spi0_miso:表示该引脚的名称。
vdd08_gpu_reg@3::这部分说明该引脚在设备树中被 vdd08_gpu_reg 节点所使用。
function lsio_iomuxc:表示该引脚的功能通过 lsio_iomuxc(IOMUX 控制器)来管理。
group lsio_gpio1_5:该引脚属于 lsio_gpio1_5 组,说明该引脚被复用为 GPIO 功能。