Linux TTY子系统(二)

文摘   2024-07-17 18:35   广东  

关注+星号公众号,不容错过精彩

作者:HywelStar

本章节主要对Linux 下tty 相关设备,以serial进行一些分析;

  • Kernel version:kernel 5.10

根据之前的章节对TTY的整个框架有一些简单了解,常说的串口驱动也一并进行简单说明,对tty调用至串口相关的整个流程进行讲解。回顾第一章节:

点这里:Linux TTY子系统(一)

1. Linux 串口驱动

Linux 串口整个驱动在嵌入式中使用率是非常非常高的一个部分,常用用于打印调试。先查看串口驱动的整个层次结构情况,主要是串口驱动与tty驱动的关系紧密。

对于TTY Core 层中,已经在Linux tty子系统介绍(一)已经有大致提到,主要是为用户层提供相关接口,为下层各种不一样的tty 进行管理;

Serial Core 层:Serial Core 层提供了一个抽象层,用于表示和处理串口设备。它定义了一组通用的接口和数据结构,使得不同类型的串口设备能够在同一接口上进行交互。

Uart driver 层:UART Driver Layer 提供了一个硬件抽象层,允许不同型号和厂商的 UART 控制器以统一的方式与 Linux 内核交互。这种抽象层使得 Linux 内核能够适应各种不同的硬件实现。

1.1 Uart 驱动注册流程

串口驱动的注册主要包含:UART驱动注册,platform 平台注册

UART驱动注册:

uart_register_driver(&imx_uart_uart_driver);

注册流程图

从注册流程来看,最终会注册到一个TTY设备,将会在文件系统产生一个tty 设备,这样可以提供给应用程进行调用控制UART.

主要代码接口uart_register_driver

关于代码调用相关:

这里引用TTY 驱动设备创建过程:

platform 平台注册:

platform_driver_register(&imx_uart_platform_driver);

这一步注册了一个平台驱动。imx_uart_platform_driver 是一个 platform_driver 结构体的实例,它定义了平台驱动的属性和操作函数,包括匹配、探测、移除等操作。平台驱动负责与具体的硬件平台进行交互。

主要用于总线匹配:

通过 platform_driver_registerplatform_driver 结构体注册到内核,内核因此知道有一个新的平台驱动可以处理特定的平台设备。

1.2 uart 操作函数

串口驱动的 uart_ops 结构体包含了uart 的操作函数。在不同平台SoC需要实现该操作函数。

static const struct uart_ops imx_uart_pops = {    .tx_empty       = imx_uart_tx_empty,      // 检查发送缓冲区是否为空    .set_mctrl      = imx_uart_set_mctrl,     // 设置调制解调器控制信号    .get_mctrl      = imx_uart_get_mctrl,     // 获取调制解调器控制信号    .stop_tx        = imx_uart_stop_tx,       // 停止发送数据    .start_tx       = imx_uart_start_tx,      // 开始发送数据    .stop_rx        = imx_uart_stop_rx,       // 停止接收数据    .enable_ms      = imx_uart_enable_ms,     // 启用调制解调器状态中断    .break_ctl      = imx_uart_break_ctl,     // 控制串口的断点信号    .startup        = imx_uart_startup,       // 启动串口    .shutdown       = imx_uart_shutdown,      // 关闭串口    .flush_buffer   = imx_uart_flush_buffer,  // 刷新发送和接收缓冲区    .set_termios    = imx_uart_set_termios,   // 设置串口的参数(波特率、数据位、停止位等)    .type           = imx_uart_type,          // 获取串口类型    .config_port    = imx_uart_config_port,   // 配置串口端口    .verify_port    = imx_uart_verify_port,   // 验证串口端口#if defined(CONFIG_CONSOLE_POLL)    .poll_init      = imx_uart_poll_init,     // 初始化轮询模式    .poll_get_char  = imx_uart_poll_get_char, // 在轮询模式下获取字符    .poll_put_char  = imx_uart_poll_put_char, // 在轮询模式下发送字符#endif};

假如在RK3588平台,同样需要实现uart_ops的操作函数。

1.3 line discipline

在架构图中可以发现有一个line discipline,这个作用是什么?

line discipline是线路规划意思,是TTY子系统中的一个比较重要概念,它在字符设备和用户空间之间扮演了一个中间层的角色,处理从用户空间传入的数据和从硬件传出的数据。它的主要作用是提供一个可插拔的处理机制,使得不同的协议和数据处理方式可以方便地集成到串口驱动中。

line discipline代码中结构体 tty_ldisc_ops 代表不同的线路规程(line discipline),每种线路规程实现了一种特定的数据处理方式或协议。

比如:

N_TTY:默认线路规划,用于标准的字符终端输入输出处理。它处理基本的控制字符(如回车、删除等),并提供行缓冲和行编辑功能。

N_PPP:用于点对点协议(PPP)处理,适用于通过串口进行的网络连接。它负责处理 PPP 的帧封装和解封装。

N_SLIP:用于串行线路互联网协议(SLIP)处理。SLIP 是一种简单的封装协议,用于在串行接口上传输 IP 数据包。

N_R3964:用于西门子的 R3964 协议,通常用于工业控制系统。它实现了西门子硬件设备的通信协议。

最常用的就是n_tty,源码路径:drivers/tty/n_tty.c

static struct tty_ldisc_ops n_tty_ops = {        .magic           = TTY_LDISC_MAGIC,        .name            = "n_tty",        .open            = n_tty_open,        .close           = n_tty_close,        .flush_buffer    = n_tty_flush_buffer,        .read            = n_tty_read,        .write           = n_tty_write,        .ioctl           = n_tty_ioctl,        .set_termios     = n_tty_set_termios,        .poll            = n_tty_poll,        .receive_buf     = n_tty_receive_buf,        .write_wakeup    = n_tty_write_wakeup,        .receive_buf2    = n_tty_receive_buf2,};

2. Linux tty应用层使用

对于TTY 应用层中的UART 来说,可以发现并没有像其他通信串口IIC,SPI具体设备驱动,关于具体设备一般都在应用层实现。对于 应用层中只有简单的一些操作。

2.1 UART 操作步骤

打开串口设备

配置串口参数

读写数据

关闭串口设备

这几个步骤 需要注意在配置串口举例子:

#include <termios.h>
struct termios options;tcgetattr(fd, &options); // 获取当前串口配置
// 设置波特率cfsetispeed(&options, B9600);cfsetospeed(&options, B9600);
// 设置控制模式options.c_cflag |= (CLOCAL | CREAD); // 允许接收数据options.c_cflag &= ~PARENB; // 无校验options.c_cflag &= ~CSTOPB; // 一位停止位options.c_cflag &= ~CSIZE; // 清除数据位掩码options.c_cflag |= CS8; // 数据位 8
// 设置输入模式options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控制
// 设置输出模式options.c_oflag &= ~OPOST; // 原始输出模式
// 设置本地模式options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始输入模式
// 设置新的串口配置tcsetattr(fd, TCSANOW, &options);

关于读写操作普通的write read等。

2.2 UART 使用注意要点

使用串口通信需要注意的是:

波特率的问题:需要与通信接口的波特率一直既可。

阻塞和非阻塞模式:在阻塞模式下,读写操作会一直等待直到完成;在非阻塞模式下,读写操作会立即返回,应用程序需要使用轮询或其他方法来检查是否有数据可用或是否已经完成写入。

串口设备文件权限:确保应用程序对串口设备文件有足够的访问权限,否则可能无法打开串口。

3. 总结

本文主要以串口驱动去介绍TTY 相关的知识,主要对串口驱动注册流程,一些重要操作函数进行简单分析,对于应用层来说是一个非常通用的用法,不过在遇到一些通信不上的时候,通过一些排查手法进行查看问题。
往期回顾:

Linux TTY 子系统介绍(一)

Linux I2C通用接口介绍(二)

Linux I2C通信接口介绍(一)

更多......


码思途远
一位码农的日常分享,专注嵌入式领域相关知识。
 最新文章