底层串口 API¶
本文档旨在简要概述新串口驱动程序的某些方面。它并不完整,您有任何问题都应直接发送至 <rmk@arm.linux.org.uk>
参考实现包含在 amba-pl011.c 中。
底层串口硬件驱动程序¶
底层串口硬件驱动程序负责向核心串口驱动程序提供端口信息(由 uart_port 定义)和一组控制方法(由 uart_ops 定义)。底层驱动程序还负责处理端口的中断,并提供任何控制台支持。
控制台支持¶
串口核心提供了一些辅助函数。这包括识别正确的端口结构(通过uart_get_console()
)和解码命令行参数 (uart_parse_options()
)。
还有一个辅助函数 (uart_console_write()
),它执行逐字符写入,将换行符转换为 CRLF 序列。建议驱动程序编写者使用此函数,而不是实现自己的版本。
锁定¶
底层硬件驱动程序有责任使用 port->lock 执行必要的锁定。有一些例外(在下面的 struct uart_ops
列表中描述)。
有两种锁。每个端口的自旋锁和一个整体信号量。
从核心驱动程序的角度来看,port->lock 锁定以下数据
port->mctrl
port->icount
port->state->xmit.head (circ_buf->head)
port->state->xmit.tail (circ_buf->tail)
底层驱动程序可以自由使用此锁来提供任何其他锁定。
port_sem 信号量用于防止端口在不适当的时间被添加/删除或重新配置。自 v2.6.27 以来,此信号量一直是 tty_port 结构体的“mutex”成员,通常称为端口互斥锁。
uart_ops¶
-
struct uart_ops¶
serial_core 和驱动程序之间的接口
定义:
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*start_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new, const struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *);
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL;
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif;
};
成员
tx_empty
unsigned int ()(struct uart_port *port)
此函数测试 **port** 的发送器 FIFO 和移位器是否为空。如果为空,则此函数应返回
TIOCSER_TEMT
,否则返回 0。如果端口不支持此操作,则应返回TIOCSER_TEMT
。锁定:无。中断:调用者依赖。此调用不得休眠
set_mctrl
void ()(struct uart_port *port, unsigned int mctrl)
此函数将 **port** 的调制解调器控制线设置为 **mctrl** 所描述的状态。**mctrl** 的相关位是
TIOCM_RTS
RTS 信号。TIOCM_DTR
DTR 信号。TIOCM_OUT1
OUT1 信号。TIOCM_OUT2
OUT2 信号。TIOCM_LOOP
将端口设置为环回模式。
如果设置了相应的位,则应将信号驱动为活动状态。如果该位已清除,则应将信号驱动为非活动状态。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
get_mctrl
unsigned int ()(struct uart_port *port)
返回 **port** 的调制解调器控制输入的当前状态。不应返回输出的状态,因为核心会跟踪其状态。状态信息应包括
TIOCM_CAR
DCD 信号的状态TIOCM_CTS
CTS 信号的状态TIOCM_DSR
DSR 信号的状态TIOCM_RI
RI 信号的状态
如果信号当前处于活动状态,则设置该位。如果端口不支持 CTS、DCD 或 DSR,则驱动程序应指示该信号永久处于活动状态。如果 RI 不可用,则不应指示该信号处于活动状态。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
stop_tx
void ()(struct uart_port *port)
停止发送字符。这可能是由于 CTS 线变为不活动状态,或者 tty 层指示我们由于
XOFF
字符而要停止发送。驱动程序应尽快停止发送字符。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
start_tx
void ()(struct uart_port *port)
开始发送字符。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
throttle
void ()(struct uart_port *port)
通知串行驱动程序,线路规程的输入缓冲区即将满,应以某种方式发出信号,指示不应再向串行端口发送字符。仅当启用硬件辅助流量控制时,才会调用此函数。
锁定:通过 tty 层的 **unthrottle()** 和 termios 修改进行序列化。
unthrottle
void ()(struct uart_port *port)
通知串行驱动程序,现在可以向串行端口发送字符,而不必担心线路规程的输入缓冲区溢出。
仅当启用硬件辅助流量控制时,才会调用此函数。
锁定:通过 tty 层的 **throttle()** 和 termios 修改进行序列化。
send_xchar
void ()(struct uart_port *port, char ch)
即使端口已停止,也发送高优先级字符。这用于实现 XON/XOFF 流量控制和 tcflow()。如果串行驱动程序未实现此函数,则 tty 核心会将该字符附加到循环缓冲区,然后调用 start_tx() / stop_tx() 以刷新数据。
如果 **ch** == '0' (
__DISABLED_CHAR
),则不发送。锁定:无。中断:调用者依赖。
stop_rx
void ()(struct uart_port *port)
停止接收字符;端口正在关闭过程中。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
start_rx
void ()(struct uart_port *port)
开始接收字符。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
enable_ms
void ()(struct uart_port *port)
启用调制解调器状态中断。
此方法可以多次调用。当调用 shutdown() 方法时,应禁用调制解调器状态中断。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
break_ctl
void ()(struct uart_port *port, int ctl)
控制中断信号的传输。如果 ctl 非零,则应传输中断信号。当使用零 ctl 再次调用时,应终止该信号。
锁定:调用者持有 tty_port->mutex
startup
int ()(struct uart_port *port)
获取任何中断资源并初始化任何底层驱动程序状态。启用端口进行接收。它不应激活 RTS 或 DTR;这将通过单独调用 set_mctrl() 来完成。
此方法仅在端口最初打开时调用。
锁定:已获取 port_sem。中断:全局禁用。
shutdown
void ()(struct uart_port *port)
禁用 port,禁用可能生效的任何中断条件,并释放任何中断资源。它不应禁用 RTS 或 DTR;这将通过单独调用 set_mctrl() 来完成。
一旦此调用完成,驱动程序不得访问 port->state。
此方法仅在没有更多用户使用此 port 时调用。
锁定:已获取 port_sem。中断:调用者依赖。
flush_buffer
void ()(struct uart_port *port)
刷新任何写入缓冲区,重置任何 DMA 状态并停止任何正在进行的 DMA 传输。
每当清除 port->state->xmit 循环缓冲区时,都会调用此方法。
锁定:已获取 **port->lock**。中断:本地禁用。此调用不得休眠
set_termios
void ()(struct uart_port *port, struct ktermios *new, struct ktermios *old)
更改 port 参数,包括字长、奇偶校验、停止位。更新 port->read_status_mask 和 port->ignore_status_mask 以指示我们有兴趣接收的事件类型。相关的 ktermios::c_cflag 位是
CSIZE
- 字大小CSTOPB
- 2 个停止位PARENB
- 启用奇偶校验PARODD
- 奇校验(当PARENB
生效时)ADDRB
- 地址位(通过 uart_port::rs485_config() 更改)。CREAD
- 启用字符接收(如果未设置,仍从端口接收字符,但会丢弃它们)。CRTSCTS
- 如果设置,启用 CTS 状态更改报告。CLOCAL
- 如果未设置,启用调制解调器状态更改报告。
相关的 ktermios::c_iflag 位是
INPCK
- 启用传递给 TTY 层的帧和奇偶校验错误事件。BRKINT
/PARMRK
- 这两个都启用传递给 TTY 层的中断事件。IGNPAR
- 忽略奇偶校验和帧错误。IGNBRK
- 忽略中断错误。如果也设置了IGNPAR
,则也忽略溢出错误。
ktermios::c_iflag 位的交互如下(以奇偶校验错误为例)
奇偶校验错误
INPCK
IGNPAR
不适用
0
不适用
接收到字符,标记为
TTY_NORMAL
无
1
不适用
接收到字符,标记为
TTY_NORMAL
是
1
0
接收到字符,标记为
TTY_PARITY
是
1
1
字符被丢弃
如果您的硬件支持硬件“软”流控制,则可以使用其他标志(例如,xon/xoff 字符)。
锁定:调用者持有 tty_port->mutex。中断:调用者依赖。此调用不得休眠
set_ldisc
void ()(struct uart_port *port, struct ktermios *termios)
线路规程更改的通知器。请参阅 TTY 线路规程。
锁定:调用者持有 tty_port->mutex
pm
void ()(struct uart_port *port, unsigned int state, unsigned int oldstate)
对指定的 port 执行任何电源管理相关活动。state 指示新状态(由枚举 uart_pm_state 定义),oldstate 指示之前的状态。
此函数不应用于获取任何资源。
当最初打开和最终关闭 port 时(除非 port 也是系统控制台)将调用此函数。即使未设置
CONFIG_PM
,也会发生这种情况。锁定:无。中断:调用者依赖。
type
const char *()(struct uart_port *port)
返回指向描述指定 port 的字符串常量的指针,或者返回
NULL
,在这种情况下,将替换字符串“unknown”。锁定:无。中断:调用者依赖。
release_port
void ()(struct uart_port *port)
释放 port 当前使用的任何内存和 IO 区域资源。
锁定:无。中断:调用者依赖。
request_port
int ()(struct uart_port *port)
请求端口所需的任何内存和 IO 区域资源。如果任何资源失败,则当此函数返回时,不应注册任何资源,并且应在失败时返回 -
EBUSY
。锁定:无。中断:调用者依赖。
config_port
void ()(struct uart_port *port, int type)
执行 port 所需的任何自动配置步骤。type 包含所需配置的位掩码。
UART_CONFIG_TYPE
指示端口需要检测和识别。port->type 应设置为找到的类型,如果没有检测到端口,则设置为PORT_UNKNOWN
。UART_CONFIG_IRQ
指示中断信号的自动配置,应使用标准内核自动探测技术进行探测。在端口内部硬连线中断的平台(例如,片上系统实现)上,这不是必需的。锁定:无。中断:调用者依赖。
verify_port
int ()(struct uart_port *port, struct serial_struct *serinfo)
验证 serinfo 中包含的新串口信息是否适合此端口类型。
锁定:无。中断:调用者依赖。
ioctl
int ()(struct uart_port *port, unsigned int cmd, unsigned long arg)
执行任何特定于端口的 IOCTL。IOCTL 命令必须使用 <asm/ioctl.h> 中找到的标准编号系统定义。
锁定:无。中断:调用者依赖。
poll_init
int ()(struct uart_port *port)
由 kgdb 调用以执行支持 poll_put_char() 和 poll_get_char() 所需的最小硬件初始化。与 startup() 不同,这不应请求中断。
锁定:已获取
tty_mutex
和 tty_port->mutex。中断:不适用。poll_put_char
void ()(struct uart_port *port, unsigned char ch)
由 kgdb 调用,以将单个字符 ch 直接写入串行 port。它可以并且应该阻塞,直到 TX FIFO 中有空间。
锁定:无。中断:调用者依赖。此调用不得休眠
poll_get_char
int ()(struct uart_port *port)
由 kgdb 调用,以直接从串行端口读取单个字符。如果数据可用,则应返回;否则,该函数应立即返回
NO_POLL_CHAR
。锁定:无。中断:调用者依赖。此调用不得休眠
描述
此结构描述了可以在物理硬件上执行的所有操作。
其他函数¶
-
void uart_write_wakeup(struct uart_port *port)¶
计划写入处理
参数
struct uart_port *port
要处理的端口
描述
此例程由中断处理程序使用,以计划驱动程序软件中断部分的处理。当发送缓冲区中的字符数降至阈值以下时,驱动程序应调用此函数。
锁定:应持有 port->lock
-
void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud)¶
更新每个端口的帧定时信息
参数
struct uart_port *port
描述端口的 uart_port 结构
unsigned int cflag
termios cflag 值
unsigned int baud
端口速度
描述
设置 port 帧定时信息,从中派生出 FIFO 超时值。cflag 值应反映实际的硬件设置,因为此处考虑了位数、奇偶校验、停止位和波特率。
锁定:调用者应持有 port->lock
-
unsigned int uart_get_baud_rate(struct uart_port *port, struct ktermios *termios, const struct ktermios *old, unsigned int min, unsigned int max)¶
返回特定端口的波特率
参数
struct uart_port *port
uart_port 结构体,描述所涉及的端口。
struct ktermios *termios
期望的 termios 设置
const struct ktermios *old
旧的 termios 设置 (或
NULL
)unsigned int min
最小可接受波特率
unsigned int max
最大可接受波特率
描述
将 termios 结构体解码为数值波特率,考虑魔术般的 38400 波特率 (带有 spd_* 标志),并将 B0
速率映射为 9600 波特。
如果新的波特率无效,则尝试使用**旧的** termios 设置。如果仍然无效,则尝试 9600 波特。如果这也无效,则返回 0。
**termios** 结构体会更新以反映我们实际要使用的波特率。对于请求 B0(“挂断”)的情况,不要这样做。
锁定:取决于调用者
-
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baud)¶
返回 uart 时钟分频值
参数
struct uart_port *port
描述端口的 uart_port 结构
unsigned int baud
期望的波特率
描述
计算指定**波特率**的分频值 (baud_base / baud),并进行适当的四舍五入。
如果选择 38400 波特率和自定义分频值,则返回自定义分频值。
锁定:取决于调用者
-
int uart_get_lsr_info(struct tty_struct *tty, struct uart_state *state, unsigned int __user *value)¶
获取线路状态寄存器信息
参数
struct tty_struct *tty
与 UART 关联的 tty
struct uart_state *state
正在查询的 UART
unsigned int __user *value
返回的调制解调器值
-
void uart_console_write(struct uart_port *port, const char *s, unsigned int count, void (*putchar)(struct uart_port*, unsigned char))¶
向串行端口写入控制台消息
参数
struct uart_port *port
要写入消息的端口
const char *s
字符数组
unsigned int count
要写入的字符串中的字符数
void (*putchar)(struct uart_port *, unsigned char)
将字符写入端口的函数
-
struct uart_port *uart_get_console(struct uart_port *ports, int nr, struct console *co)¶
获取控制台的 uart 端口
参数
struct uart_port *ports
要搜索的端口
int nr
**端口**数量
struct console *co
要搜索的控制台
返回
控制台 **co** 的 uart_port
描述
检查是否已指定无效的 uart 编号 (作为 **co->index**),如果是,则搜索第一个支持控制台的可用端口。
-
int uart_parse_earlycon(char *p, unsigned char *iotype, resource_size_t *addr, char **options)¶
解析 earlycon 选项
参数
char *p
指向第二个字段的指针 (即,刚好在 '<name>,' 之后)
unsigned char *iotype
用于解码 iotype 的指针(输出)
resource_size_t *addr
用于解码 mapbase/iobase 的指针(输出)
char **options
用于 <options> 字段的指针;如果不存在,则为
NULL
(输出)
描述
- 解码形式如下的 earlycon 内核命令行参数
earlycon=<name>,io|mmio|mmio16|mmio32|mmio32be|mmio32native,<addr>,<options>
console=<name>,io|mmio|mmio16|mmio32|mmio32be|mmio32native,<addr>,<options>
- 可选形式
earlycon=<name>,0x<addr>,<options>
console=<name>,0x<addr>,<options>
也被接受;返回的 **iotype** 将为 UPIO_MEM
。
返回
成功返回 0,失败返回 -EINVAL
-
void uart_parse_options(const char *options, int *baud, int *parity, int *bits, int *flow)¶
解析串行端口波特率/奇偶校验/数据位/流控制。
参数
const char *options
指向选项字符串的指针
int *baud
指向波特率的“int”变量的指针。
int *parity
指向奇偶校验的“int”变量的指针。
int *bits
指向数据位数的“int”变量的指针。
int *flow
指向流控制字符的“int”变量的指针。
描述
uart_parse_options()
解码包含串行控制台选项的字符串。字符串的格式为 <波特率><奇偶校验><数据位><流控制>,例如:115200n8r
-
int uart_set_options(struct uart_port *port, struct console *co, int baud, int parity, int bits, int flow)¶
设置串行控制台参数
参数
struct uart_port *port
指向串行端口 uart_port 结构的指针
struct console *co
控制台指针
int baud
波特率
int parity
奇偶校验字符 - “n”(无)、“o”(奇数)、“e”(偶数)
int bits
数据位数
int flow
流控制字符 - ‘r’ (rts)
描述
锁定:调用者必须持有 console_list_lock 才能序列化串行控制台锁的早期初始化。
-
int uart_register_driver(struct uart_driver *drv)¶
向 uart 核心层注册一个驱动程序
参数
struct uart_driver *drv
底层驱动程序结构
描述
向核心驱动程序注册一个 uart 驱动程序。我们反过来向 tty 层注册,并初始化每个端口的核心驱动程序状态。
我们在 /proc/tty/driver 中有一个 proc 文件,它以普通驱动程序命名。
drv->port 应该为 NULL
,并且在调用成功后,应使用 uart_add_one_port() 注册每个端口的结构。
锁定:无,中断:已启用
-
void uart_unregister_driver(struct uart_driver *drv)¶
从 uart 核心层删除一个驱动程序
参数
struct uart_driver *drv
底层驱动程序结构
描述
从核心驱动程序中删除对驱动程序的所有引用。如果底层驱动程序使用 uart_add_one_port() 注册了其所有端口,则必须通过 uart_remove_one_port() 删除它们。(即 drv->port 为 NULL
。)
锁定:无,中断:已启用
-
bool uart_match_port(const struct uart_port *port1, const struct uart_port *port2)¶
两个端口是否等效?
参数
const struct uart_port *port1
第一个端口
const struct uart_port *port2
第二个端口
描述
此实用函数可用于确定两个 uart_port 结构是否描述同一个端口。
-
void uart_handle_dcd_change(struct uart_port *uport, bool active)¶
处理载波检测状态的更改
参数
struct uart_port *uport
打开端口的 uart_port 结构
bool active
新的载波检测状态
描述
调用者必须持有 uport->lock。
-
void uart_handle_cts_change(struct uart_port *uport, bool active)¶
处理清除发送状态的更改
参数
struct uart_port *uport
打开端口的 uart_port 结构
bool active
新的清除发送状态
描述
调用者必须持有 uport->lock。
-
bool uart_try_toggle_sysrq(struct uart_port *port, u8 ch)¶
从串行线路启用 SysRq
参数
struct uart_port *port
在遇到 BREAK 后,字符所在的 uart_port 结构
u8 ch
接收到 BREAK 后序列中的新字符
描述
当在端口上满足所需的序列时(请参阅 CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE),启用 magic SysRq。
返回
如果 ch 不在启用序列中并且应以其他方式处理,则为 false
;如果 ch 已被消耗,则为 true
。
-
uart_port_tx_limited¶
uart_port_tx_limited (port, ch, count, tx_ready, put_char, tx_done)
用于限制计数的 uart_port 的传输辅助函数
参数
port
uart 端口
ch
用于存储要写入硬件的字符的变量
count
要发送的字符数限制
tx_ready
硬件是否可以接受更多数据的功能
put_char
写入字符的功能
tx_done
循环完成后要调用的函数
描述
此辅助函数使用 put_char() 将 xmit 缓冲区中的字符传输到硬件。它会一直执行此操作,直到发送了 count 个字符并且 tx_ready 的求值结果为 true。
- 宏参数中的表达式应按如下方式设计
tx_ready:如果硬件可以接受更多要发送的数据,则应求值为 true。此参数可以是
true
,这意味着硬件始终准备就绪。put_char:应将 ch 写入 port 的设备。
tx_done:当写入循环完成时,这可以在可能调用 ops->stop_tx() 之前执行任意操作。如果驱动程序不需要执行任何操作,请使用例如 ({})。
对于所有这些,都持有 port->lock,本地禁用中断,并且表达式不得休眠。
返回
完成时 xmit 缓冲区中的字符数。
-
uart_port_tx¶
uart_port_tx (port, ch, tx_ready, put_char)
用于 uart_port 的传输辅助函数
参数
port
uart 端口
ch
用于存储要写入硬件的字符的变量
tx_ready
硬件是否可以接受更多数据的功能
put_char
写入字符的功能
描述
有关更多详细信息,请参阅 uart_port_tx_limited()
。
其他说明¶
有一天,我们计划从 uart_port 中删除“未使用”的条目,并允许底层驱动程序向核心注册其自己的单个 uart_port。这将允许驱动程序使用 uart_port 作为指向包含 uart_port 条目及其自身扩展的结构的指针,从而
struct my_port {
struct uart_port port;
int my_stuff;
};
通过 GPIO 的调制解调器控制线¶
提供了一些辅助函数,以便通过 GPIO 设置/获取调制解调器控制线。
-
void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)¶
根据 mctrl 状态设置 gpios
参数
struct mctrl_gpios *gpios
要设置的 gpios
unsigned int mctrl
要设置的状态
描述
根据 mctrl 状态设置 gpios。
-
struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios, enum mctrl_gpio_idx gidx)¶
获取调制解调器线路索引的 gpio_desc
参数
struct mctrl_gpios *gpios
要查找的 gpios
enum mctrl_gpio_idx gidx
调制解调器线路的索引
返回
与调制解调器线路索引关联的 gpio_desc 结构
-
unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)¶
使用 gpios 值更新 mctrl。
参数
struct mctrl_gpios *gpios
要从中获取信息的 gpios
unsigned int *mctrl
要设置的 mctrl
返回
修改后的 mctrl(与 mctrl 中的值相同)
描述
使用 GPIO 值更新 mctrl。
-
struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)¶
初始化 UART GPIO。
参数
struct uart_port *port
要为其初始化 GPIO 的端口。
unsigned int idx
GPIO 在 port 的设备中的索引。
描述
如果设备树中存在 {cts,rts,...}-gpios,此函数将从设备树获取它们并请求它们,设置方向等,并返回一个已分配的结构。devm_* 函数被使用,因此无需调用 mctrl_gpio_free()
。由于此函数设置了 IRQ 处理,请确保不要在你的驱动程序中处理 GPIO 输入行的更改。
参数
struct device *dev
UART 端口的设备。
struct mctrl_gpios *gpios
要释放的 GPIO 结构。
描述
这将释放 mctrl_gpio_init()
中请求的 GPIO。由于使用了 devm_* 函数,通常不需要调用此函数。
-
void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)¶
启用 IRQ 和对 MS 行更改的处理。
参数
struct mctrl_gpios *gpios
要启用的 GPIO。
-
void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)¶
禁用 IRQ 和对 MS 行更改的处理。
参数
struct mctrl_gpios *gpios
要禁用的 GPIO。