底层串口 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_maskport->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->portNULL。)

锁定:无,中断:已启用

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 输入行的更改。

void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)

显式释放 UART 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。