底层串行 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 正在关闭。

锁定:已获取 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

n/a

0

n/a

接收到的字符,标记为 TTY_NORMAL

1

n/a

接收到的字符,标记为 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 指示新状态(由 enum 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。 中断:n/a。

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 波特。

如果新的波特率无效,请尝试 old 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 的除数 (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

ports 的数量

struct console *co

要搜索的控制台

返回值

控制台 co 的 uart_port

说明

检查是否已指定无效的 uart 编号(作为 co->index),如果是,则搜索第一个具有控制台支持的可用端口。

int uart_parse_earlycon(char *p, enum uart_iotype *iotype, resource_size_t *addr, char **options)

解析 earlycon 选项

参数

char *p

指向第二个字段的指针(即,刚刚超过“<name>,”)

enum uart_iotype *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 后遇到 char(s) 的 uart_port 结构

u8 ch

收到 BREAK 后序列中的新字符

说明

当端口上满足所需序列时,启用 magic SysRq(请参阅 CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE)。

返回值

如果 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

用于存储要写入 HW 的字符的变量

count

要发送的字符数限制

tx_ready

HW 是否可以接受更多数据函数

put_char

写入字符的函数

tx_done

循环完成后要调用的函数

说明

此助手使用 put_char() 将字符从 xmit 缓冲区传输到硬件。这样做直到发送 count 个字符,并且当 tx_ready 的计算结果为 true 时。

宏参数中的表达式应设计如下
  • tx_ready:如果 HW 可以接受更多要发送的数据,则应评估为 true。此参数可以是 true,这意味着 HW 始终准备就绪。

  • 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

用于存储要写入 HW 的字符的变量

tx_ready

HW 是否可以接受更多数据函数

put_char

写入字符的函数

说明

有关更多详细信息,请参见 uart_port_tx_limited()

其他说明

目标是在未来的某一天从 uart_port 中删除“unused”条目,并允许底层驱动程序向核心注册它们自己的 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 中的值相同)

说明

使用 gpios 值更新 mctrl。

struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)

初始化 uart gpios

参数

struct uart_port *port

为其初始化 gpios 的端口

unsigned int idx

端口设备中 gpio 的索引

说明

如果设备树中存在 {cts,rts,...}-gpios,并且它们被请求,则将从设备树中获取它们,设置方向等,并返回分配的结构。使用 devm_* 函数,因此无需显式释放。由于这会设置 irq 处理,请确保也不要在驱动程序中处理 gpio 输入线的更改。

void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)

启用 irqs 并处理 ms 线的更改

参数

struct mctrl_gpios *gpios

要启用的 gpios

void mctrl_gpio_disable_ms_sync(struct mctrl_gpios *gpios)

禁用 irqs 并处理 ms 线的更改,并等待处理任何挂起的 IRQ

参数

struct mctrl_gpios *gpios

要禁用的 gpios

void mctrl_gpio_disable_ms_no_sync(struct mctrl_gpios *gpios)

禁用 irqs 并处理 ms 线的更改,并立即返回

参数

struct mctrl_gpios *gpios

要禁用的 gpios